/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* vim: set ts=8 sts=2 et sw=2 tw=80: *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. *///// This file implements a garbage-cycle collector based on the paper//// Concurrent Cycle Collection in Reference Counted Systems// Bacon & Rajan (2001), ECOOP 2001 / Springer LNCS vol 2072//// We are not using the concurrent or acyclic cases of that paper; so// the green, red and orange colors are not used.//// The collector is based on tracking pointers of four colors://// Black nodes are definitely live. If we ever determine a node is// black, it's ok to forget about, drop from our records.//// White nodes are definitely garbage cycles. Once we finish with our// scanning, we unlink all the white nodes and expect that by// unlinking them they will self-destruct (since a garbage cycle is// only keeping itself alive with internal links, by definition).//// Snow-white is an addition to the original algorithm. Snow-white object// has reference count zero and is just waiting for deletion.//// Grey nodes are being scanned. Nodes that turn grey will turn// either black if we determine that they're live, or white if we// determine that they're a garbage cycle. After the main collection// algorithm there should be no grey nodes.//// Purple nodes are *candidates* for being scanned. They are nodes we// haven't begun scanning yet because they're not old enough, or we're// still partway through the algorithm.//// XPCOM objects participating in garbage-cycle collection are obliged// to inform us when they ought to turn purple; that is, when their// refcount transitions from N+1 -> N, for nonzero N. Furthermore we// require that *after* an XPCOM object has informed us of turning// purple, they will tell us when they either transition back to being// black (incremented refcount) or are ultimately deleted.// Incremental cycle collection//// Beyond the simple state machine required to implement incremental// collection, the CC needs to be able to compensate for things the browser// is doing during the collection. There are two kinds of problems. For each// of these, there are two cases to deal with: purple-buffered C++ objects// and JS objects.// The first problem is that an object in the CC's graph can become garbage.// This is bad because the CC touches the objects in its graph at every// stage of its operation.//// All cycle collected C++ objects that die during a cycle collection// will end up actually getting deleted by the SnowWhiteKiller. Before// the SWK deletes an object, it checks if an ICC is running, and if so,// if the object is in the graph. If it is, the CC clears mPointer and// mParticipant so it does not point to the raw object any more. Because// objects could die any time the CC returns to the mutator, any time the CC// accesses a PtrInfo it must perform a null check on mParticipant to// ensure the object has not gone away.//// JS objects don't always run finalizers, so the CC can't remove them from// the graph when they die. Fortunately, JS objects can only die during a GC,// so if a GC is begun during an ICC, the browser synchronously finishes off// the ICC, which clears the entire CC graph. If the GC and CC are scheduled// properly, this should be rare.//// The second problem is that objects in the graph can be changed, say by// being addrefed or released, or by having a field updated, after the object// has been added to the graph. The problem is that ICC can miss a newly// created reference to an object, and end up unlinking an object that is// actually alive.//// The basic idea of the solution, from "An on-the-fly Reference Counting// Garbage Collector for Java" by Levanoni and Petrank, is to notice if an// object has had an additional reference to it created during the collection,// and if so, don't collect it during the current collection. This avoids having// to rerun the scan as in Bacon & Rajan 2001.//// For cycle collected C++ objects, we modify AddRef to place the object in// the purple buffer, in addition to Release. Then, in the CC, we treat any// objects in the purple buffer as being alive, after graph building has// completed. Because they are in the purple buffer, they will be suspected// in the next CC, so there's no danger of leaks. This is imprecise, because// we will treat as live an object that has been Released but not AddRefed// during graph building, but that's probably rare enough that the additional// bookkeeping overhead is not worthwhile.//// For JS objects, the cycle collector is only looking at gray objects. If a// gray object is touched during ICC, it will be made black by UnmarkGray.// Thus, if a JS object has become black during the ICC, we treat it as live.// Merged JS zones have to be handled specially: we scan all zone globals.// If any are black, we treat the zone as being black.// Safety//// An XPCOM object is either scan-safe or scan-unsafe, purple-safe or// purple-unsafe.//// An nsISupports object is scan-safe if://// - It can be QI'ed to |nsXPCOMCycleCollectionParticipant|, though// this operation loses ISupports identity (like nsIClassInfo).// - Additionally, the operation |traverse| on the resulting// nsXPCOMCycleCollectionParticipant does not cause *any* refcount// adjustment to occur (no AddRef / Release calls).//// A non-nsISupports ("native") object is scan-safe by explicitly// providing its nsCycleCollectionParticipant.//// An object is purple-safe if it satisfies the following properties://// - The object is scan-safe.//// When we receive a pointer |ptr| via// |nsCycleCollector::suspect(ptr)|, we assume it is purple-safe. We// can check the scan-safety, but have no way to ensure the// purple-safety; objects must obey, or else the entire system falls// apart. Don't involve an object in this scheme if you can't// guarantee its purple-safety. The easiest way to ensure that an// object is purple-safe is to use nsCycleCollectingAutoRefCnt.//// When we have a scannable set of purple nodes ready, we begin// our walks. During the walks, the nodes we |traverse| should only// feed us more scan-safe nodes, and should not adjust the refcounts// of those nodes.//// We do not |AddRef| or |Release| any objects during scanning. We// rely on the purple-safety of the roots that call |suspect| to// hold, such that we will clear the pointer from the purple buffer// entry to the object before it is destroyed. The pointers that are// merely scan-safe we hold only for the duration of scanning, and// there should be no objects released from the scan-safe set during// the scan.//// We *do* call |Root| and |Unroot| on every white object, on// either side of the calls to |Unlink|. This keeps the set of white// objects alive during the unlinking.//#if !defined(__MINGW32__)#ifdef WIN32#include<crtdbg.h>#include<errno.h>#endif#endif#include"base/process_util.h"#include"mozilla/ArrayUtils.h"#include"mozilla/AutoRestore.h"#include"mozilla/CycleCollectedJSContext.h"#include"mozilla/CycleCollectedJSRuntime.h"#include"mozilla/DebugOnly.h"#include"mozilla/HoldDropJSObjects.h"/* This must occur *after* base/process_util.h to avoid typedefs conflicts. */#include"mozilla/LinkedList.h"#include"mozilla/MemoryReporting.h"#include"mozilla/Move.h"#include"mozilla/SegmentedVector.h"#include"nsCycleCollectionParticipant.h"#include"nsCycleCollectionNoteRootCallback.h"#include"nsDeque.h"#include"nsCycleCollector.h"#include"nsThreadUtils.h"#include"nsXULAppAPI.h"#include"prenv.h"#include"nsPrintfCString.h"#include"nsTArray.h"#include"nsIConsoleService.h"#include"mozilla/Attributes.h"#include"nsICycleCollectorListener.h"#include"nsISerialEventTarget.h"#include"nsIMemoryReporter.h"#include"nsIFile.h"#include"nsDumpUtils.h"#include"xpcpublic.h"#include"GeckoProfiler.h"#include<stdint.h>#include<stdio.h>#include"mozilla/AutoGlobalTimelineMarker.h"#include"mozilla/Likely.h"#include"mozilla/PoisonIOInterposer.h"#include"mozilla/Telemetry.h"#include"mozilla/ThreadLocal.h"#ifdef MOZ_CRASHREPORTER#include"nsExceptionHandler.h"#endifusingnamespacemozilla;//#define COLLECT_TIME_DEBUG// Enable assertions that are useful for diagnosing errors in graph construction.//#define DEBUG_CC_GRAPH#define DEFAULT_SHUTDOWN_COLLECTIONS 5// One to do the freeing, then another to detect there is no more work to do.#define NORMAL_SHUTDOWN_COLLECTIONS 2// Cycle collector environment variables//// MOZ_CC_LOG_ALL: If defined, always log cycle collector heaps.//// MOZ_CC_LOG_SHUTDOWN: If defined, log cycle collector heaps at shutdown.//// MOZ_CC_LOG_THREAD: If set to "main", only automatically log main thread// CCs. If set to "worker", only automatically log worker CCs. If set to "all",// log either. The default value is "all". This must be used with either// MOZ_CC_LOG_ALL or MOZ_CC_LOG_SHUTDOWN for it to do anything.//// MOZ_CC_LOG_PROCESS: If set to "main", only automatically log main process// CCs. If set to "content", only automatically log tab CCs. If set to// "plugins", only automatically log plugin CCs. If set to "all", log// everything. The default value is "all". This must be used with either// MOZ_CC_LOG_ALL or MOZ_CC_LOG_SHUTDOWN for it to do anything.//// MOZ_CC_ALL_TRACES: If set to "all", any cycle collector// logging done will be WantAllTraces, which disables// various cycle collector optimizations to give a fuller picture of// the heap. If set to "shutdown", only shutdown logging will be WantAllTraces.// The default is none.//// MOZ_CC_RUN_DURING_SHUTDOWN: In non-DEBUG or builds, if this is set,// run cycle collections at shutdown.//// MOZ_CC_LOG_DIRECTORY: The directory in which logs are placed (such as// logs from MOZ_CC_LOG_ALL and MOZ_CC_LOG_SHUTDOWN, or other uses// of nsICycleCollectorListener)// Various parameters of this collector can be tuned using environment// variables.structnsCycleCollectorParams{boolmLogAll;boolmLogShutdown;boolmAllTracesAll;boolmAllTracesShutdown;boolmLogThisThread;nsCycleCollectorParams():mLogAll(PR_GetEnv("MOZ_CC_LOG_ALL")!=nullptr),mLogShutdown(PR_GetEnv("MOZ_CC_LOG_SHUTDOWN")!=nullptr),mAllTracesAll(false),mAllTracesShutdown(false){constchar*logThreadEnv=PR_GetEnv("MOZ_CC_LOG_THREAD");boolthreadLogging=true;if(logThreadEnv&&!!strcmp(logThreadEnv,"all")){if(NS_IsMainThread()){threadLogging=!strcmp(logThreadEnv,"main");}else{threadLogging=!strcmp(logThreadEnv,"worker");}}constchar*logProcessEnv=PR_GetEnv("MOZ_CC_LOG_PROCESS");boolprocessLogging=true;if(logProcessEnv&&!!strcmp(logProcessEnv,"all")){switch(XRE_GetProcessType()){caseGeckoProcessType_Default:processLogging=!strcmp(logProcessEnv,"main");break;caseGeckoProcessType_Plugin:processLogging=!strcmp(logProcessEnv,"plugins");break;caseGeckoProcessType_Content:processLogging=!strcmp(logProcessEnv,"content");break;default:processLogging=false;break;}}mLogThisThread=threadLogging&&processLogging;constchar*allTracesEnv=PR_GetEnv("MOZ_CC_ALL_TRACES");if(allTracesEnv){if(!strcmp(allTracesEnv,"all")){mAllTracesAll=true;}elseif(!strcmp(allTracesEnv,"shutdown")){mAllTracesShutdown=true;}}}boolLogThisCC(boolaIsShutdown){return(mLogAll||(aIsShutdown&&mLogShutdown))&&mLogThisThread;}boolAllTracesThisCC(boolaIsShutdown){returnmAllTracesAll||(aIsShutdown&&mAllTracesShutdown);}};#ifdef COLLECT_TIME_DEBUGclassTimeLog{public:TimeLog():mLastCheckpoint(TimeStamp::Now()){}voidCheckpoint(constchar*aEvent){TimeStampnow=TimeStamp::Now();doubledur=(now-mLastCheckpoint).ToMilliseconds();if(dur>=0.5){printf("cc: %s took %.1fms\n",aEvent,dur);}mLastCheckpoint=now;}private:TimeStampmLastCheckpoint;};#elseclassTimeLog{public:TimeLog(){}voidCheckpoint(constchar*aEvent){}};#endif////////////////////////////////////////////////////////////////////////// Base types////////////////////////////////////////////////////////////////////////structPtrInfo;classEdgePool{public:// EdgePool allocates arrays of void*, primarily to hold PtrInfo*.// However, at the end of a block, the last two pointers are a null// and then a void** pointing to the next block. This allows// EdgePool::Iterators to be a single word but still capable of crossing// block boundaries.EdgePool(){mSentinelAndBlocks[0].block=nullptr;mSentinelAndBlocks[1].block=nullptr;}~EdgePool(){MOZ_ASSERT(!mSentinelAndBlocks[0].block&&!mSentinelAndBlocks[1].block,"Didn't call Clear()?");}voidClear(){EdgeBlock*b=EdgeBlocks();while(b){EdgeBlock*next=b->Next();deleteb;b=next;}mSentinelAndBlocks[0].block=nullptr;mSentinelAndBlocks[1].block=nullptr;}#ifdef DEBUGboolIsEmpty(){return!mSentinelAndBlocks[0].block&&!mSentinelAndBlocks[1].block;}#endifprivate:structEdgeBlock;unionPtrInfoOrBlock{// Use a union to avoid reinterpret_cast and the ensuing// potential aliasing bugs.PtrInfo*ptrInfo;EdgeBlock*block;};structEdgeBlock{enum{EdgeBlockSize=16*1024};PtrInfoOrBlockmPointers[EdgeBlockSize];EdgeBlock(){mPointers[EdgeBlockSize-2].block=nullptr;// sentinelmPointers[EdgeBlockSize-1].block=nullptr;// next block pointer}EdgeBlock*&Next(){returnmPointers[EdgeBlockSize-1].block;}PtrInfoOrBlock*Start(){return&mPointers[0];}PtrInfoOrBlock*End(){return&mPointers[EdgeBlockSize-2];}};// Store the null sentinel so that we can have valid iterators// before adding any edges and without adding any blocks.PtrInfoOrBlockmSentinelAndBlocks[2];EdgeBlock*&EdgeBlocks(){returnmSentinelAndBlocks[1].block;}EdgeBlock*EdgeBlocks()const{returnmSentinelAndBlocks[1].block;}public:classIterator{public:Iterator():mPointer(nullptr){}explicitIterator(PtrInfoOrBlock*aPointer):mPointer(aPointer){}Iterator(constIterator&aOther):mPointer(aOther.mPointer){}Iterator&operator++(){if(!mPointer->ptrInfo){// Null pointer is a sentinel for link to the next block.mPointer=(mPointer+1)->block->mPointers;}++mPointer;return*this;}PtrInfo*operator*()const{if(!mPointer->ptrInfo){// Null pointer is a sentinel for link to the next block.return(mPointer+1)->block->mPointers->ptrInfo;}returnmPointer->ptrInfo;}booloperator==(constIterator&aOther)const{returnmPointer==aOther.mPointer;}booloperator!=(constIterator&aOther)const{returnmPointer!=aOther.mPointer;}#ifdef DEBUG_CC_GRAPHboolInitialized()const{returnmPointer!=nullptr;}#endifprivate:PtrInfoOrBlock*mPointer;};classBuilder;friendclassBuilder;classBuilder{public:explicitBuilder(EdgePool&aPool):mCurrent(&aPool.mSentinelAndBlocks[0]),mBlockEnd(&aPool.mSentinelAndBlocks[0]),mNextBlockPtr(&aPool.EdgeBlocks()){}IteratorMark(){returnIterator(mCurrent);}voidAdd(PtrInfo*aEdge){if(mCurrent==mBlockEnd){EdgeBlock*b=newEdgeBlock();*mNextBlockPtr=b;mCurrent=b->Start();mBlockEnd=b->End();mNextBlockPtr=&b->Next();}(mCurrent++)->ptrInfo=aEdge;}private:// mBlockEnd points to space for null sentinelPtrInfoOrBlock*mCurrent;PtrInfoOrBlock*mBlockEnd;EdgeBlock**mNextBlockPtr;};size_tSizeOfExcludingThis(MallocSizeOfaMallocSizeOf)const{size_tn=0;EdgeBlock*b=EdgeBlocks();while(b){n+=aMallocSizeOf(b);b=b->Next();}returnn;}};#ifdef DEBUG_CC_GRAPH#define CC_GRAPH_ASSERT(b) MOZ_ASSERT(b)#else#define CC_GRAPH_ASSERT(b)#endif#define CC_TELEMETRY(_name, _value) \ do { \ if (NS_IsMainThread()) { \ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR##_name, _value); \ } else { \ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_WORKER##_name, _value); \ } \ } while(0)enumNodeColor{black,white,grey};// This structure should be kept as small as possible; we may expect// hundreds of thousands of them to be allocated and touched// repeatedly during each cycle collection.structPtrInfo{void*mPointer;nsCycleCollectionParticipant*mParticipant;uint32_tmColor:2;uint32_tmInternalRefs:30;uint32_tmRefCount;private:EdgePool::IteratormFirstChild;staticconstuint32_tkInitialRefCount=UINT32_MAX-1;public:PtrInfo(void*aPointer,nsCycleCollectionParticipant*aParticipant):mPointer(aPointer),mParticipant(aParticipant),mColor(grey),mInternalRefs(0),mRefCount(kInitialRefCount),mFirstChild(){MOZ_ASSERT(aParticipant);// We initialize mRefCount to a large non-zero value so// that it doesn't look like a JS object to the cycle collector// in the case where the object dies before being traversed.MOZ_ASSERT(!IsGrayJS()&&!IsBlackJS());}// Allow NodePool::NodeBlock's constructor to compile.PtrInfo(){NS_NOTREACHED("should never be called");}boolIsGrayJS()const{returnmRefCount==0;}boolIsBlackJS()const{returnmRefCount==UINT32_MAX;}boolWasTraversed()const{returnmRefCount!=kInitialRefCount;}EdgePool::IteratorFirstChild()const{CC_GRAPH_ASSERT(mFirstChild.Initialized());returnmFirstChild;}// this PtrInfo must be part of a NodePoolEdgePool::IteratorLastChild()const{CC_GRAPH_ASSERT((this+1)->mFirstChild.Initialized());return(this+1)->mFirstChild;}voidSetFirstChild(EdgePool::IteratoraFirstChild){CC_GRAPH_ASSERT(aFirstChild.Initialized());mFirstChild=aFirstChild;}// this PtrInfo must be part of a NodePoolvoidSetLastChild(EdgePool::IteratoraLastChild){CC_GRAPH_ASSERT(aLastChild.Initialized());(this+1)->mFirstChild=aLastChild;}};/** * A structure designed to be used like a linked list of PtrInfo, except * it allocates many PtrInfos at a time. */classNodePool{private:// The -2 allows us to use |NodeBlockSize + 1| for |mEntries|, and fit// |mNext|, all without causing slop.enum{NodeBlockSize=4*1024-2};structNodeBlock{// We create and destroy NodeBlock using moz_xmalloc/free rather than new// and delete to avoid calling its constructor and destructor.NodeBlock(){NS_NOTREACHED("should never be called");// Ensure NodeBlock is the right size (see the comment on NodeBlockSize// above).static_assert(sizeof(NodeBlock)==81904||// 32-bit; equals 19.996 x 4 KiB pagessizeof(NodeBlock)==131048,// 64-bit; equals 31.994 x 4 KiB pages"ill-sized NodeBlock");}~NodeBlock(){NS_NOTREACHED("should never be called");}NodeBlock*mNext;PtrInfomEntries[NodeBlockSize+1];// +1 to store last child of last node};public:NodePool():mBlocks(nullptr),mLast(nullptr){}~NodePool(){MOZ_ASSERT(!mBlocks,"Didn't call Clear()?");}voidClear(){NodeBlock*b=mBlocks;while(b){NodeBlock*n=b->mNext;free(b);b=n;}mBlocks=nullptr;mLast=nullptr;}#ifdef DEBUGboolIsEmpty(){return!mBlocks&&!mLast;}#endifclassBuilder;friendclassBuilder;classBuilder{public:explicitBuilder(NodePool&aPool):mNextBlock(&aPool.mBlocks),mNext(aPool.mLast),mBlockEnd(nullptr){MOZ_ASSERT(!aPool.mBlocks&&!aPool.mLast,"pool not empty");}PtrInfo*Add(void*aPointer,nsCycleCollectionParticipant*aParticipant){if(mNext==mBlockEnd){NodeBlock*block=static_cast<NodeBlock*>(malloc(sizeof(NodeBlock)));if(!block){returnnullptr;}*mNextBlock=block;mNext=block->mEntries;mBlockEnd=block->mEntries+NodeBlockSize;block->mNext=nullptr;mNextBlock=&block->mNext;}returnnew(mozilla::KnownNotNull,mNext++)PtrInfo(aPointer,aParticipant);}private:NodeBlock**mNextBlock;PtrInfo*&mNext;PtrInfo*mBlockEnd;};classEnumerator;friendclassEnumerator;classEnumerator{public:explicitEnumerator(NodePool&aPool):mFirstBlock(aPool.mBlocks),mCurBlock(nullptr),mNext(nullptr),mBlockEnd(nullptr),mLast(aPool.mLast){}boolIsDone()const{returnmNext==mLast;}boolAtBlockEnd()const{returnmNext==mBlockEnd;}PtrInfo*GetNext(){MOZ_ASSERT(!IsDone(),"calling GetNext when done");if(mNext==mBlockEnd){NodeBlock*nextBlock=mCurBlock?mCurBlock->mNext:mFirstBlock;mNext=nextBlock->mEntries;mBlockEnd=mNext+NodeBlockSize;mCurBlock=nextBlock;}returnmNext++;}private:// mFirstBlock is a reference to allow an Enumerator to be constructed// for an empty graph.NodeBlock*&mFirstBlock;NodeBlock*mCurBlock;// mNext is the next value we want to return, unless mNext == mBlockEnd// NB: mLast is a reference to allow enumerating while building!PtrInfo*mNext;PtrInfo*mBlockEnd;PtrInfo*&mLast;};size_tSizeOfExcludingThis(MallocSizeOfaMallocSizeOf)const{// We don't measure the things pointed to by mEntries[] because those// pointers are non-owning.size_tn=0;NodeBlock*b=mBlocks;while(b){n+=aMallocSizeOf(b);b=b->mNext;}returnn;}private:NodeBlock*mBlocks;PtrInfo*mLast;};// Declarations for mPtrToNodeMap.structPtrToNodeEntry:publicPLDHashEntryHdr{// The key is mNode->mPointerPtrInfo*mNode;};staticboolPtrToNodeMatchEntry(constPLDHashEntryHdr*aEntry,constvoid*aKey){constPtrToNodeEntry*n=static_cast<constPtrToNodeEntry*>(aEntry);returnn->mNode->mPointer==aKey;}staticPLDHashTableOpsPtrNodeOps={PLDHashTable::HashVoidPtrKeyStub,PtrToNodeMatchEntry,PLDHashTable::MoveEntryStub,PLDHashTable::ClearEntryStub,nullptr};structWeakMapping{// map and key will be null if the corresponding objects are GC markedPtrInfo*mMap;PtrInfo*mKey;PtrInfo*mKeyDelegate;PtrInfo*mVal;};classCCGraphBuilder;structCCGraph{NodePoolmNodes;EdgePoolmEdges;nsTArray<WeakMapping>mWeakMaps;uint32_tmRootCount;private:PLDHashTablemPtrToNodeMap;boolmOutOfMemory;staticconstuint32_tkInitialMapLength=16384;public:CCGraph():mRootCount(0),mPtrToNodeMap(&PtrNodeOps,sizeof(PtrToNodeEntry),kInitialMapLength),mOutOfMemory(false){}~CCGraph(){}voidInit(){MOZ_ASSERT(IsEmpty(),"Failed to call CCGraph::Clear");}voidClear(){mNodes.Clear();mEdges.Clear();mWeakMaps.Clear();mRootCount=0;mPtrToNodeMap.ClearAndPrepareForLength(kInitialMapLength);mOutOfMemory=false;}#ifdef DEBUGboolIsEmpty(){returnmNodes.IsEmpty()&&mEdges.IsEmpty()&&mWeakMaps.IsEmpty()&&mRootCount==0&&mPtrToNodeMap.EntryCount()==0;}#endifPtrInfo*FindNode(void*aPtr);PtrToNodeEntry*AddNodeToMap(void*aPtr);voidRemoveObjectFromMap(void*aObject);uint32_tMapCount()const{returnmPtrToNodeMap.EntryCount();}size_tSizeOfExcludingThis(MallocSizeOfaMallocSizeOf)const{size_tn=0;n+=mNodes.SizeOfExcludingThis(aMallocSizeOf);n+=mEdges.SizeOfExcludingThis(aMallocSizeOf);// We don't measure what the WeakMappings point to, because the// pointers are non-owning.n+=mWeakMaps.ShallowSizeOfExcludingThis(aMallocSizeOf);n+=mPtrToNodeMap.ShallowSizeOfExcludingThis(aMallocSizeOf);returnn;}private:PtrToNodeEntry*FindNodeEntry(void*aPtr){returnstatic_cast<PtrToNodeEntry*>(mPtrToNodeMap.Search(aPtr));}};PtrInfo*CCGraph::FindNode(void*aPtr){PtrToNodeEntry*e=FindNodeEntry(aPtr);returne?e->mNode:nullptr;}PtrToNodeEntry*CCGraph::AddNodeToMap(void*aPtr){JS::AutoSuppressGCAnalysissuppress;if(mOutOfMemory){returnnullptr;}autoe=static_cast<PtrToNodeEntry*>(mPtrToNodeMap.Add(aPtr,fallible));if(!e){mOutOfMemory=true;MOZ_ASSERT(false,"Ran out of memory while building cycle collector graph");returnnullptr;}returne;}voidCCGraph::RemoveObjectFromMap(void*aObj){PtrToNodeEntry*e=FindNodeEntry(aObj);PtrInfo*pinfo=e?e->mNode:nullptr;if(pinfo){mPtrToNodeMap.RemoveEntry(e);pinfo->mPointer=nullptr;pinfo->mParticipant=nullptr;}}staticnsISupports*CanonicalizeXPCOMParticipant(nsISupports*aIn){nsISupports*out=nullptr;aIn->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),reinterpret_cast<void**>(&out));returnout;}staticinlinevoidToParticipant(nsISupports*aPtr,nsXPCOMCycleCollectionParticipant**aCp);staticvoidCanonicalizeParticipant(void**aParti,nsCycleCollectionParticipant**aCp){// If the participant is null, this is an nsISupports participant,// so we must QI to get the real participant.if(!*aCp){nsISupports*nsparti=static_cast<nsISupports*>(*aParti);nsparti=CanonicalizeXPCOMParticipant(nsparti);NS_ASSERTION(nsparti,"Don't add objects that don't participate in collection!");nsXPCOMCycleCollectionParticipant*xcp;ToParticipant(nsparti,&xcp);*aParti=nsparti;*aCp=xcp;}}structnsPurpleBufferEntry{nsPurpleBufferEntry(void*aObject,nsCycleCollectingAutoRefCnt*aRefCnt,nsCycleCollectionParticipant*aParticipant):mObject(aObject),mRefCnt(aRefCnt),mParticipant(aParticipant){}nsPurpleBufferEntry(nsPurpleBufferEntry&&aOther):mObject(nullptr),mRefCnt(nullptr),mParticipant(nullptr){Swap(aOther);}voidSwap(nsPurpleBufferEntry&aOther){std::swap(mObject,aOther.mObject);std::swap(mRefCnt,aOther.mRefCnt);std::swap(mParticipant,aOther.mParticipant);}voidClear(){mRefCnt->RemoveFromPurpleBuffer();mRefCnt=nullptr;mObject=nullptr;mParticipant=nullptr;}~nsPurpleBufferEntry(){if(mRefCnt){mRefCnt->RemoveFromPurpleBuffer();}}void*mObject;nsCycleCollectingAutoRefCnt*mRefCnt;nsCycleCollectionParticipant*mParticipant;// nullptr for nsISupports};classnsCycleCollector;structnsPurpleBuffer{private:uint32_tmCount;// Try to match the size of a jemalloc bucket, to minimize slop bytes.// - On 32-bit platforms sizeof(nsPurpleBufferEntry) is 12, so mEntries'// Segment is 16,372 bytes.// - On 64-bit platforms sizeof(nsPurpleBufferEntry) is 24, so mEntries'// Segment is 32,760 bytes.staticconstuint32_tkEntriesPerSegment=1365;staticconstsize_tkSegmentSize=sizeof(nsPurpleBufferEntry)*kEntriesPerSegment;typedefSegmentedVector<nsPurpleBufferEntry,kSegmentSize,InfallibleAllocPolicy>PurpleBufferVector;PurpleBufferVectormEntries;public:nsPurpleBuffer():mCount(0){static_assert(sizeof(PurpleBufferVector::Segment)==16372||// 32-bitsizeof(PurpleBufferVector::Segment)==32760||// 64-bitsizeof(PurpleBufferVector::Segment)==32744,// 64-bit Windows"ill-sized nsPurpleBuffer::mEntries");}~nsPurpleBuffer(){}// This method compacts mEntries.template<classPurpleVisitor>voidVisitEntries(PurpleVisitor&aVisitor){if(mEntries.IsEmpty()){return;}uint32_toldLength=mEntries.Length();uint32_tnewLength=0;autorevIter=mEntries.IterFromLast();autoiter=mEntries.Iter();// After iteration this points to the first empty entry.autofirstEmptyIter=mEntries.Iter();autoiterFromLastEntry=mEntries.IterFromLast();for(;!iter.Done();iter.Next()){nsPurpleBufferEntry&e=iter.Get();if(e.mObject){if(!aVisitor.Visit(*this,&e)){return;}}// Visit call above may have cleared the entry, or the entry was empty// already.if(!e.mObject){// Try to find a non-empty entry from the end of the vector.for(;!revIter.Done();revIter.Prev()){nsPurpleBufferEntry&otherEntry=revIter.Get();if(&e==&otherEntry){break;}if(otherEntry.mObject){if(!aVisitor.Visit(*this,&otherEntry)){return;}// Visit may have cleared otherEntry.if(otherEntry.mObject){e.Swap(otherEntry);revIter.Prev();// We've swapped this now empty entry.break;}}}}// Entry is non-empty even after the Visit call, ensure it is kept// in mEntries.if(e.mObject){firstEmptyIter.Next();++newLength;}if(&e==&revIter.Get()){break;}}// There were some empty entries.if(oldLength!=newLength){// While visiting entries, some new ones were possibly added. This can// happen during CanSkip. Move all such new entries to be after other// entries. Note, we don't call Visit on newly added entries!if(&iterFromLastEntry.Get()!=&mEntries.GetLast()){iterFromLastEntry.Next();// Now pointing to the first added entry.auto&iterForNewEntries=iterFromLastEntry;while(!iterForNewEntries.Done()){MOZ_ASSERT(!firstEmptyIter.Done());MOZ_ASSERT(!firstEmptyIter.Get().mObject);firstEmptyIter.Get().Swap(iterForNewEntries.Get());firstEmptyIter.Next();iterForNewEntries.Next();++newLength;// We keep all the new entries.}}mEntries.PopLastN(oldLength-newLength);}}voidFreeBlocks(){mCount=0;mEntries.Clear();}voidSelectPointers(CCGraphBuilder&aBuilder);// RemoveSkippable removes entries from the purple buffer synchronously// (1) if aAsyncSnowWhiteFreeing is false and nsPurpleBufferEntry::mRefCnt is 0 or// (2) if the object's nsXPCOMCycleCollectionParticipant::CanSkip() returns true or// (3) if nsPurpleBufferEntry::mRefCnt->IsPurple() is false.// (4) If removeChildlessNodes is true, then any nodes in the purple buffer// that will have no children in the cycle collector graph will also be// removed. CanSkip() may be run on these children.voidRemoveSkippable(nsCycleCollector*aCollector,js::SliceBudget&aBudget,boolaRemoveChildlessNodes,boolaAsyncSnowWhiteFreeing,CC_ForgetSkippableCallbackaCb);MOZ_ALWAYS_INLINEvoidPut(void*aObject,nsCycleCollectionParticipant*aCp,nsCycleCollectingAutoRefCnt*aRefCnt){nsPurpleBufferEntryentry(aObject,aRefCnt,aCp);Unused<<mEntries.Append(Move(entry));MOZ_ASSERT(!entry.mRefCnt,"Move didn't work!");++mCount;}voidRemove(nsPurpleBufferEntry*aEntry){MOZ_ASSERT(mCount!=0,"must have entries");--mCount;aEntry->Clear();}uint32_tCount()const{returnmCount;}size_tSizeOfExcludingThis(MallocSizeOfaMallocSizeOf)const{returnmEntries.SizeOfExcludingThis(aMallocSizeOf);}};staticboolAddPurpleRoot(CCGraphBuilder&aBuilder,void*aRoot,nsCycleCollectionParticipant*aParti);structSelectPointersVisitor{explicitSelectPointersVisitor(CCGraphBuilder&aBuilder):mBuilder(aBuilder){}boolVisit(nsPurpleBuffer&aBuffer,nsPurpleBufferEntry*aEntry){MOZ_ASSERT(aEntry->mObject,"Null object in purple buffer");MOZ_ASSERT(aEntry->mRefCnt->get()!=0,"SelectPointersVisitor: snow-white object in the purple buffer");if(!aEntry->mRefCnt->IsPurple()||AddPurpleRoot(mBuilder,aEntry->mObject,aEntry->mParticipant)){aBuffer.Remove(aEntry);}returntrue;}private:CCGraphBuilder&mBuilder;};voidnsPurpleBuffer::SelectPointers(CCGraphBuilder&aBuilder){SelectPointersVisitorvisitor(aBuilder);VisitEntries(visitor);MOZ_ASSERT(mCount==0,"AddPurpleRoot failed");if(mCount==0){FreeBlocks();}}enumccPhase{IdlePhase,GraphBuildingPhase,ScanAndCollectWhitePhase,CleanupPhase};enumccType{SliceCC,/* If a CC is in progress, continue it. Otherwise, start a new one. */ManualCC,/* Explicitly triggered. */ShutdownCC/* Shutdown CC, used for finding leaks. */};////////////////////////////////////////////////////////////////////////// Top level structure for the cycle collector.////////////////////////////////////////////////////////////////////////usingjs::SliceBudget;classJSPurpleBuffer;classnsCycleCollector:publicnsIMemoryReporter{public:NS_DECL_ISUPPORTSNS_DECL_NSIMEMORYREPORTERprivate:boolmActivelyCollecting;boolmFreeingSnowWhite;// mScanInProgress should be false when we're collecting white objects.boolmScanInProgress;CycleCollectorResultsmResults;TimeStampmCollectionStart;CycleCollectedJSRuntime*mCCJSRuntime;ccPhasemIncrementalPhase;CCGraphmGraph;nsAutoPtr<CCGraphBuilder>mBuilder;RefPtr<nsCycleCollectorLogger>mLogger;#ifdef DEBUGnsISerialEventTarget*mEventTarget;#endifnsCycleCollectorParamsmParams;uint32_tmWhiteNodeCount;CC_BeforeUnlinkCallbackmBeforeUnlinkCB;CC_ForgetSkippableCallbackmForgetSkippableCB;nsPurpleBuffermPurpleBuf;uint32_tmUnmergedNeeded;uint32_tmMergedInARow;RefPtr<JSPurpleBuffer>mJSPurpleBuffer;private:virtual~nsCycleCollector();public:nsCycleCollector();voidSetCCJSRuntime(CycleCollectedJSRuntime*aCCRuntime);voidClearCCJSRuntime();voidSetBeforeUnlinkCallback(CC_BeforeUnlinkCallbackaBeforeUnlinkCB){CheckThreadSafety();mBeforeUnlinkCB=aBeforeUnlinkCB;}voidSetForgetSkippableCallback(CC_ForgetSkippableCallbackaForgetSkippableCB){CheckThreadSafety();mForgetSkippableCB=aForgetSkippableCB;}voidSuspect(void*aPtr,nsCycleCollectionParticipant*aCp,nsCycleCollectingAutoRefCnt*aRefCnt);uint32_tSuspectedCount();voidForgetSkippable(js::SliceBudget&aBudget,boolaRemoveChildlessNodes,boolaAsyncSnowWhiteFreeing);boolFreeSnowWhite(boolaUntilNoSWInPurpleBuffer);// This method assumes its argument is already canonicalized.voidRemoveObjectFromGraph(void*aPtr);voidPrepareForGarbageCollection();voidFinishAnyCurrentCollection();boolCollect(ccTypeaCCType,SliceBudget&aBudget,nsICycleCollectorListener*aManualListener,boolaPreferShorterSlices=false);voidShutdown(boolaDoCollect);boolIsIdle()const{returnmIncrementalPhase==IdlePhase;}voidSizeOfIncludingThis(mozilla::MallocSizeOfaMallocSizeOf,size_t*aObjectSize,size_t*aGraphSize,size_t*aPurpleBufferSize)const;JSPurpleBuffer*GetJSPurpleBuffer();CycleCollectedJSRuntime*Runtime(){returnmCCJSRuntime;}private:voidCheckThreadSafety();voidShutdownCollect();voidFixGrayBits(boolaForceGC,TimeLog&aTimeLog);boolIsIncrementalGCInProgress();voidFinishAnyIncrementalGCInProgress();boolShouldMergeZones(ccTypeaCCType);voidBeginCollection(ccTypeaCCType,nsICycleCollectorListener*aManualListener);voidMarkRoots(SliceBudget&aBudget);voidScanRoots(boolaFullySynchGraphBuild);voidScanIncrementalRoots();voidScanWhiteNodes(boolaFullySynchGraphBuild);voidScanBlackNodes();voidScanWeakMaps();// returns whether anything was collectedboolCollectWhite();voidCleanupAfterCollection();};NS_IMPL_ISUPPORTS(nsCycleCollector,nsIMemoryReporter)/** * GraphWalker is templatized over a Visitor class that must provide * the following two methods: * * bool ShouldVisitNode(PtrInfo const *pi); * void VisitNode(PtrInfo *pi); */template<classVisitor>classGraphWalker{private:VisitormVisitor;voidDoWalk(nsDeque&aQueue);voidCheckedPush(nsDeque&aQueue,PtrInfo*aPi){if(!aPi){MOZ_CRASH();}if(!aQueue.Push(aPi,fallible)){mVisitor.Failed();}}public:voidWalk(PtrInfo*aPi);voidWalkFromRoots(CCGraph&aGraph);// copy-constructing the visitor should be cheap, and less// indirection than using a referenceexplicitGraphWalker(constVisitoraVisitor):mVisitor(aVisitor){}};////////////////////////////////////////////////////////////////////////// The static collector struct////////////////////////////////////////////////////////////////////////structCollectorData{RefPtr<nsCycleCollector>mCollector;CycleCollectedJSContext*mContext;};staticMOZ_THREAD_LOCAL(CollectorData*)sCollectorData;////////////////////////////////////////////////////////////////////////// Utility functions////////////////////////////////////////////////////////////////////////staticinlinevoidToParticipant(nsISupports*aPtr,nsXPCOMCycleCollectionParticipant**aCp){// We use QI to move from an nsISupports to an// nsXPCOMCycleCollectionParticipant, which is a per-class singleton helper// object that implements traversal and unlinking logic for the nsISupports// in question.*aCp=nullptr;CallQueryInterface(aPtr,aCp);}template<classVisitor>MOZ_NEVER_INLINEvoidGraphWalker<Visitor>::Walk(PtrInfo*aPi){nsDequequeue;CheckedPush(queue,aPi);DoWalk(queue);}template<classVisitor>MOZ_NEVER_INLINEvoidGraphWalker<Visitor>::WalkFromRoots(CCGraph&aGraph){nsDequequeue;NodePool::Enumeratoretor(aGraph.mNodes);for(uint32_ti=0;i<aGraph.mRootCount;++i){CheckedPush(queue,etor.GetNext());}DoWalk(queue);}template<classVisitor>MOZ_NEVER_INLINEvoidGraphWalker<Visitor>::DoWalk(nsDeque&aQueue){// Use a aQueue to match the breadth-first traversal used when we// built the graph, for hopefully-better locality.while(aQueue.GetSize()>0){PtrInfo*pi=static_cast<PtrInfo*>(aQueue.PopFront());if(pi->WasTraversed()&&mVisitor.ShouldVisitNode(pi)){mVisitor.VisitNode(pi);for(EdgePool::Iteratorchild=pi->FirstChild(),child_end=pi->LastChild();child!=child_end;++child){CheckedPush(aQueue,*child);}}}}structCCGraphDescriber:publicLinkedListElement<CCGraphDescriber>{CCGraphDescriber():mAddress("0x"),mCnt(0),mType(eUnknown){}enumType{eRefCountedObject,eGCedObject,eGCMarkedObject,eEdge,eRoot,eGarbage,eUnknown};nsCStringmAddress;nsCStringmName;nsCStringmCompartmentOrToAddress;uint32_tmCnt;TypemType;};classLogStringMessageAsync:publicCancelableRunnable{public:explicitLogStringMessageAsync(constnsAString&aMsg):mozilla::CancelableRunnable("LogStringMessageAsync"),mMsg(aMsg){}NS_IMETHODRun()override{nsCOMPtr<nsIConsoleService>cs=do_GetService(NS_CONSOLESERVICE_CONTRACTID);if(cs){cs->LogStringMessage(mMsg.get());}returnNS_OK;}private:nsStringmMsg;};classnsCycleCollectorLogSinkToFilefinal:publicnsICycleCollectorLogSink{public:NS_DECL_ISUPPORTSnsCycleCollectorLogSinkToFile():mProcessIdentifier(base::GetCurrentProcId()),mGCLog("gc-edges"),mCCLog("cc-edges"){}NS_IMETHODGetFilenameIdentifier(nsAString&aIdentifier)override{aIdentifier=mFilenameIdentifier;returnNS_OK;}NS_IMETHODSetFilenameIdentifier(constnsAString&aIdentifier)override{mFilenameIdentifier=aIdentifier;returnNS_OK;}NS_IMETHODGetProcessIdentifier(int32_t*aIdentifier)override{*aIdentifier=mProcessIdentifier;returnNS_OK;}NS_IMETHODSetProcessIdentifier(int32_taIdentifier)override{mProcessIdentifier=aIdentifier;returnNS_OK;}NS_IMETHODGetGcLog(nsIFile**aPath)override{NS_IF_ADDREF(*aPath=mGCLog.mFile);returnNS_OK;}NS_IMETHODGetCcLog(nsIFile**aPath)override{NS_IF_ADDREF(*aPath=mCCLog.mFile);returnNS_OK;}NS_IMETHODOpen(FILE**aGCLog,FILE**aCCLog)override{nsresultrv;if(mGCLog.mStream||mCCLog.mStream){returnNS_ERROR_UNEXPECTED;}rv=OpenLog(&mGCLog);NS_ENSURE_SUCCESS(rv,rv);*aGCLog=mGCLog.mStream;rv=OpenLog(&mCCLog);NS_ENSURE_SUCCESS(rv,rv);*aCCLog=mCCLog.mStream;returnNS_OK;}NS_IMETHODCloseGCLog()override{if(!mGCLog.mStream){returnNS_ERROR_UNEXPECTED;}CloseLog(&mGCLog,NS_LITERAL_STRING("Garbage"));returnNS_OK;}NS_IMETHODCloseCCLog()override{if(!mCCLog.mStream){returnNS_ERROR_UNEXPECTED;}CloseLog(&mCCLog,NS_LITERAL_STRING("Cycle"));returnNS_OK;}private:~nsCycleCollectorLogSinkToFile(){if(mGCLog.mStream){MozillaUnRegisterDebugFILE(mGCLog.mStream);fclose(mGCLog.mStream);}if(mCCLog.mStream){MozillaUnRegisterDebugFILE(mCCLog.mStream);fclose(mCCLog.mStream);}}structFileInfo{constchar*constmPrefix;nsCOMPtr<nsIFile>mFile;FILE*mStream;explicitFileInfo(constchar*aPrefix):mPrefix(aPrefix),mStream(nullptr){}};/** * Create a new file named something like aPrefix.$PID.$IDENTIFIER.log in * $MOZ_CC_LOG_DIRECTORY or in the system's temp directory. No existing * file will be overwritten; if aPrefix.$PID.$IDENTIFIER.log exists, we'll * try a file named something like aPrefix.$PID.$IDENTIFIER-1.log, and so * on. */already_AddRefed<nsIFile>CreateTempFile(constchar*aPrefix){nsPrintfCStringfilename("%s.%d%s%s.log",aPrefix,mProcessIdentifier,mFilenameIdentifier.IsEmpty()?"":".",NS_ConvertUTF16toUTF8(mFilenameIdentifier).get());// Get the log directory either from $MOZ_CC_LOG_DIRECTORY or from// the fallback directories in OpenTempFile. We don't use an nsCOMPtr// here because OpenTempFile uses an in/out param and getter_AddRefs// wouldn't work.nsIFile*logFile=nullptr;if(char*env=PR_GetEnv("MOZ_CC_LOG_DIRECTORY")){NS_NewNativeLocalFile(nsCString(env),/* followLinks = */true,&logFile);}// On Android or B2G, this function will open a file named// aFilename under a memory-reporting-specific folder// (/data/local/tmp/memory-reports). Otherwise, it will open a// file named aFilename under "NS_OS_TEMP_DIR".nsresultrv=nsDumpUtils::OpenTempFile(filename,&logFile,NS_LITERAL_CSTRING("memory-reports"));if(NS_FAILED(rv)){NS_IF_RELEASE(logFile);returnnullptr;}returndont_AddRef(logFile);}nsresultOpenLog(FileInfo*aLog){// Initially create the log in a file starting with "incomplete-".// We'll move the file and strip off the "incomplete-" once the dump// completes. (We do this because we don't want scripts which poll// the filesystem looking for GC/CC dumps to grab a file before we're// finished writing to it.)nsAutoCStringincomplete;incomplete+="incomplete-";incomplete+=aLog->mPrefix;MOZ_ASSERT(!aLog->mFile);aLog->mFile=CreateTempFile(incomplete.get());if(NS_WARN_IF(!aLog->mFile)){returnNS_ERROR_UNEXPECTED;}MOZ_ASSERT(!aLog->mStream);nsresultrv=aLog->mFile->OpenANSIFileDesc("w",&aLog->mStream);if(NS_WARN_IF(NS_FAILED(rv))){returnNS_ERROR_UNEXPECTED;}MozillaRegisterDebugFILE(aLog->mStream);returnNS_OK;}nsresultCloseLog(FileInfo*aLog,constnsAString&aCollectorKind){MOZ_ASSERT(aLog->mStream);MOZ_ASSERT(aLog->mFile);MozillaUnRegisterDebugFILE(aLog->mStream);fclose(aLog->mStream);aLog->mStream=nullptr;// Strip off "incomplete-".nsCOMPtr<nsIFile>logFileFinalDestination=CreateTempFile(aLog->mPrefix);if(NS_WARN_IF(!logFileFinalDestination)){returnNS_ERROR_UNEXPECTED;}nsAutoStringlogFileFinalDestinationName;logFileFinalDestination->GetLeafName(logFileFinalDestinationName);if(NS_WARN_IF(logFileFinalDestinationName.IsEmpty())){returnNS_ERROR_UNEXPECTED;}aLog->mFile->MoveTo(/* directory */nullptr,logFileFinalDestinationName);// Save the file path.aLog->mFile=logFileFinalDestination;// Log to the error console.nsAutoStringlogPath;logFileFinalDestination->GetPath(logPath);nsAutoStringmsg=aCollectorKind+NS_LITERAL_STRING(" Collector log dumped to ")+logPath;// We don't want any JS to run between ScanRoots and CollectWhite calls,// and since ScanRoots calls this method, better to log the message// asynchronously.RefPtr<LogStringMessageAsync>log=newLogStringMessageAsync(msg);NS_DispatchToCurrentThread(log);returnNS_OK;}int32_tmProcessIdentifier;nsStringmFilenameIdentifier;FileInfomGCLog;FileInfomCCLog;};NS_IMPL_ISUPPORTS(nsCycleCollectorLogSinkToFile,nsICycleCollectorLogSink)classnsCycleCollectorLoggerfinal:publicnsICycleCollectorListener{~nsCycleCollectorLogger(){ClearDescribers();}public:nsCycleCollectorLogger():mLogSink(nsCycleCollector_createLogSink()),mWantAllTraces(false),mDisableLog(false),mWantAfterProcessing(false),mCCLog(nullptr){}NS_DECL_ISUPPORTSvoidSetAllTraces(){mWantAllTraces=true;}boolIsAllTraces(){returnmWantAllTraces;}NS_IMETHODAllTraces(nsICycleCollectorListener**aListener)override{SetAllTraces();NS_ADDREF(*aListener=this);returnNS_OK;}NS_IMETHODGetWantAllTraces(bool*aAllTraces)override{*aAllTraces=mWantAllTraces;returnNS_OK;}NS_IMETHODGetDisableLog(bool*aDisableLog)override{*aDisableLog=mDisableLog;returnNS_OK;}NS_IMETHODSetDisableLog(boolaDisableLog)override{mDisableLog=aDisableLog;returnNS_OK;}NS_IMETHODGetWantAfterProcessing(bool*aWantAfterProcessing)override{*aWantAfterProcessing=mWantAfterProcessing;returnNS_OK;}NS_IMETHODSetWantAfterProcessing(boolaWantAfterProcessing)override{mWantAfterProcessing=aWantAfterProcessing;returnNS_OK;}NS_IMETHODGetLogSink(nsICycleCollectorLogSink**aLogSink)override{NS_ADDREF(*aLogSink=mLogSink);returnNS_OK;}NS_IMETHODSetLogSink(nsICycleCollectorLogSink*aLogSink)override{if(!aLogSink){returnNS_ERROR_INVALID_ARG;}mLogSink=aLogSink;returnNS_OK;}nsresultBegin(){nsresultrv;mCurrentAddress.AssignLiteral("0x");ClearDescribers();if(mDisableLog){returnNS_OK;}FILE*gcLog;rv=mLogSink->Open(&gcLog,&mCCLog);NS_ENSURE_SUCCESS(rv,rv);// Dump the JS heap.CollectorData*data=sCollectorData.get();if(data&&data->mContext){data->mContext->Runtime()->DumpJSHeap(gcLog);}rv=mLogSink->CloseGCLog();NS_ENSURE_SUCCESS(rv,rv);fprintf(mCCLog,"# WantAllTraces=%s\n",mWantAllTraces?"true":"false");returnNS_OK;}voidNoteRefCountedObject(uint64_taAddress,uint32_taRefCount,constchar*aObjectDescription){if(!mDisableLog){fprintf(mCCLog,"%p [rc=%u] %s\n",(void*)aAddress,aRefCount,aObjectDescription);}if(mWantAfterProcessing){CCGraphDescriber*d=newCCGraphDescriber();mDescribers.insertBack(d);mCurrentAddress.AssignLiteral("0x");mCurrentAddress.AppendInt(aAddress,16);d->mType=CCGraphDescriber::eRefCountedObject;d->mAddress=mCurrentAddress;d->mCnt=aRefCount;d->mName.Append(aObjectDescription);}}voidNoteGCedObject(uint64_taAddress,boolaMarked,constchar*aObjectDescription,uint64_taCompartmentAddress){if(!mDisableLog){fprintf(mCCLog,"%p [gc%s] %s\n",(void*)aAddress,aMarked?".marked":"",aObjectDescription);}if(mWantAfterProcessing){CCGraphDescriber*d=newCCGraphDescriber();mDescribers.insertBack(d);mCurrentAddress.AssignLiteral("0x");mCurrentAddress.AppendInt(aAddress,16);d->mType=aMarked?CCGraphDescriber::eGCMarkedObject:CCGraphDescriber::eGCedObject;d->mAddress=mCurrentAddress;d->mName.Append(aObjectDescription);if(aCompartmentAddress){d->mCompartmentOrToAddress.AssignLiteral("0x");d->mCompartmentOrToAddress.AppendInt(aCompartmentAddress,16);}else{d->mCompartmentOrToAddress.SetIsVoid(true);}}}voidNoteEdge(uint64_taToAddress,constchar*aEdgeName){if(!mDisableLog){fprintf(mCCLog,"> %p %s\n",(void*)aToAddress,aEdgeName);}if(mWantAfterProcessing){CCGraphDescriber*d=newCCGraphDescriber();mDescribers.insertBack(d);d->mType=CCGraphDescriber::eEdge;d->mAddress=mCurrentAddress;d->mCompartmentOrToAddress.AssignLiteral("0x");d->mCompartmentOrToAddress.AppendInt(aToAddress,16);d->mName.Append(aEdgeName);}}voidNoteWeakMapEntry(uint64_taMap,uint64_taKey,uint64_taKeyDelegate,uint64_taValue){if(!mDisableLog){fprintf(mCCLog,"WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n",(void*)aMap,(void*)aKey,(void*)aKeyDelegate,(void*)aValue);}// We don't support after-processing for weak map entries.}voidNoteIncrementalRoot(uint64_taAddress){if(!mDisableLog){fprintf(mCCLog,"IncrementalRoot %p\n",(void*)aAddress);}// We don't support after-processing for incremental roots.}voidBeginResults(){if(!mDisableLog){fputs("==========\n",mCCLog);}}voidDescribeRoot(uint64_taAddress,uint32_taKnownEdges){if(!mDisableLog){fprintf(mCCLog,"%p [known=%u]\n",(void*)aAddress,aKnownEdges);}if(mWantAfterProcessing){CCGraphDescriber*d=newCCGraphDescriber();mDescribers.insertBack(d);d->mType=CCGraphDescriber::eRoot;d->mAddress.AppendInt(aAddress,16);d->mCnt=aKnownEdges;}}voidDescribeGarbage(uint64_taAddress){if(!mDisableLog){fprintf(mCCLog,"%p [garbage]\n",(void*)aAddress);}if(mWantAfterProcessing){CCGraphDescriber*d=newCCGraphDescriber();mDescribers.insertBack(d);d->mType=CCGraphDescriber::eGarbage;d->mAddress.AppendInt(aAddress,16);}}voidEnd(){if(!mDisableLog){mCCLog=nullptr;Unused<<NS_WARN_IF(NS_FAILED(mLogSink->CloseCCLog()));}}NS_IMETHODProcessNext(nsICycleCollectorHandler*aHandler,bool*aCanContinue)override{if(NS_WARN_IF(!aHandler)||NS_WARN_IF(!mWantAfterProcessing)){returnNS_ERROR_UNEXPECTED;}CCGraphDescriber*d=mDescribers.popFirst();if(d){switch(d->mType){caseCCGraphDescriber::eRefCountedObject:aHandler->NoteRefCountedObject(d->mAddress,d->mCnt,d->mName);break;caseCCGraphDescriber::eGCedObject:caseCCGraphDescriber::eGCMarkedObject:aHandler->NoteGCedObject(d->mAddress,d->mType==CCGraphDescriber::eGCMarkedObject,d->mName,d->mCompartmentOrToAddress);break;caseCCGraphDescriber::eEdge:aHandler->NoteEdge(d->mAddress,d->mCompartmentOrToAddress,d->mName);break;caseCCGraphDescriber::eRoot:aHandler->DescribeRoot(d->mAddress,d->mCnt);break;caseCCGraphDescriber::eGarbage:aHandler->DescribeGarbage(d->mAddress);break;caseCCGraphDescriber::eUnknown:NS_NOTREACHED("CCGraphDescriber::eUnknown");break;}deleted;}if(!(*aCanContinue=!mDescribers.isEmpty())){mCurrentAddress.AssignLiteral("0x");}returnNS_OK;}NS_IMETHODAsLogger(nsCycleCollectorLogger**aRetVal)override{RefPtr<nsCycleCollectorLogger>rval=this;rval.forget(aRetVal);returnNS_OK;}private:voidClearDescribers(){CCGraphDescriber*d;while((d=mDescribers.popFirst())){deleted;}}nsCOMPtr<nsICycleCollectorLogSink>mLogSink;boolmWantAllTraces;boolmDisableLog;boolmWantAfterProcessing;nsCStringmCurrentAddress;mozilla::LinkedList<CCGraphDescriber>mDescribers;FILE*mCCLog;};NS_IMPL_ISUPPORTS(nsCycleCollectorLogger,nsICycleCollectorListener)nsresultnsCycleCollectorLoggerConstructor(nsISupports*aOuter,constnsIID&aIID,void**aInstancePtr){if(NS_WARN_IF(aOuter)){returnNS_ERROR_NO_AGGREGATION;}nsISupports*logger=newnsCycleCollectorLogger();returnlogger->QueryInterface(aIID,aInstancePtr);}staticboolGCThingIsGrayCCThing(JS::GCCellPtrthing){returnAddToCCKind(thing.kind())&&JS::GCThingIsMarkedGray(thing);}staticboolValueIsGrayCCThing(constJS::Value&value){returnAddToCCKind(value.traceKind())&&JS::GCThingIsMarkedGray(value.toGCCellPtr());}////////////////////////////////////////////////////////////////////////// Bacon & Rajan's |MarkRoots| routine.////////////////////////////////////////////////////////////////////////classCCGraphBuilderfinal:publicnsCycleCollectionTraversalCallback,publicnsCycleCollectionNoteRootCallback{private:CCGraph&mGraph;CycleCollectorResults&mResults;NodePool::BuildermNodeBuilder;EdgePool::BuildermEdgeBuilder;MOZ_INIT_OUTSIDE_CTORPtrInfo*mCurrPi;nsCycleCollectionParticipant*mJSParticipant;nsCycleCollectionParticipant*mJSZoneParticipant;nsCStringmNextEdgeName;RefPtr<nsCycleCollectorLogger>mLogger;boolmMergeZones;nsAutoPtr<NodePool::Enumerator>mCurrNode;public:CCGraphBuilder(CCGraph&aGraph,CycleCollectorResults&aResults,CycleCollectedJSRuntime*aCCRuntime,nsCycleCollectorLogger*aLogger,boolaMergeZones);virtual~CCGraphBuilder();boolWantAllTraces()const{returnnsCycleCollectionNoteRootCallback::WantAllTraces();}boolAddPurpleRoot(void*aRoot,nsCycleCollectionParticipant*aParti);// This is called when all roots have been added to the graph, to prepare for BuildGraph().voidDoneAddingRoots();// Do some work traversing nodes in the graph. Returns true if this graph building is finished.boolBuildGraph(SliceBudget&aBudget);private:PtrInfo*AddNode(void*aPtr,nsCycleCollectionParticipant*aParticipant);PtrInfo*AddWeakMapNode(JS::GCCellPtraThing);PtrInfo*AddWeakMapNode(JSObject*aObject);voidSetFirstChild(){mCurrPi->SetFirstChild(mEdgeBuilder.Mark());}voidSetLastChild(){mCurrPi->SetLastChild(mEdgeBuilder.Mark());}public:// nsCycleCollectionNoteRootCallback methods.NS_IMETHOD_(void)NoteXPCOMRoot(nsISupports*aRoot);NS_IMETHOD_(void)NoteJSRoot(JSObject*aRoot);NS_IMETHOD_(void)NoteNativeRoot(void*aRoot,nsCycleCollectionParticipant*aParticipant);NS_IMETHOD_(void)NoteWeakMapping(JSObject*aMap,JS::GCCellPtraKey,JSObject*aKdelegate,JS::GCCellPtraVal);// nsCycleCollectionTraversalCallback methods.NS_IMETHOD_(void)DescribeRefCountedNode(nsrefcntaRefCount,constchar*aObjName);NS_IMETHOD_(void)DescribeGCedNode(boolaIsMarked,constchar*aObjName,uint64_taCompartmentAddress);NS_IMETHOD_(void)NoteXPCOMChild(nsISupports*aChild);NS_IMETHOD_(void)NoteJSChild(constJS::GCCellPtr&aThing);NS_IMETHOD_(void)NoteNativeChild(void*aChild,nsCycleCollectionParticipant*aParticipant);NS_IMETHOD_(void)NoteNextEdgeName(constchar*aName);private:voidNoteJSChild(JS::GCCellPtraChild);NS_IMETHOD_(void)NoteRoot(void*aRoot,nsCycleCollectionParticipant*aParticipant){MOZ_ASSERT(aRoot);MOZ_ASSERT(aParticipant);if(!aParticipant->CanSkipInCC(aRoot)||MOZ_UNLIKELY(WantAllTraces())){AddNode(aRoot,aParticipant);}}NS_IMETHOD_(void)NoteChild(void*aChild,nsCycleCollectionParticipant*aCp,nsCString&aEdgeName){PtrInfo*childPi=AddNode(aChild,aCp);if(!childPi){return;}mEdgeBuilder.Add(childPi);if(mLogger){mLogger->NoteEdge((uint64_t)aChild,aEdgeName.get());}++childPi->mInternalRefs;}JS::Zone*MergeZone(JS::GCCellPtraGcthing){if(!mMergeZones){returnnullptr;}JS::Zone*zone=JS::GetTenuredGCThingZone(aGcthing);if(js::IsSystemZone(zone)){returnnullptr;}returnzone;}};CCGraphBuilder::CCGraphBuilder(CCGraph&aGraph,CycleCollectorResults&aResults,CycleCollectedJSRuntime*aCCRuntime,nsCycleCollectorLogger*aLogger,boolaMergeZones):mGraph(aGraph),mResults(aResults),mNodeBuilder(aGraph.mNodes),mEdgeBuilder(aGraph.mEdges),mJSParticipant(nullptr),mJSZoneParticipant(nullptr),mLogger(aLogger),mMergeZones(aMergeZones){if(aCCRuntime){mJSParticipant=aCCRuntime->GCThingParticipant();mJSZoneParticipant=aCCRuntime->ZoneParticipant();}if(mLogger){mFlags|=nsCycleCollectionTraversalCallback::WANT_DEBUG_INFO;if(mLogger->IsAllTraces()){mFlags|=nsCycleCollectionTraversalCallback::WANT_ALL_TRACES;mWantAllTraces=true;// for nsCycleCollectionNoteRootCallback}}mMergeZones=mMergeZones&&MOZ_LIKELY(!WantAllTraces());MOZ_ASSERT(nsCycleCollectionNoteRootCallback::WantAllTraces()==nsCycleCollectionTraversalCallback::WantAllTraces());}CCGraphBuilder::~CCGraphBuilder(){}PtrInfo*CCGraphBuilder::AddNode(void*aPtr,nsCycleCollectionParticipant*aParticipant){PtrToNodeEntry*e=mGraph.AddNodeToMap(aPtr);if(!e){returnnullptr;}PtrInfo*result;if(!e->mNode){// New entry.result=mNodeBuilder.Add(aPtr,aParticipant);if(!result){returnnullptr;}e->mNode=result;NS_ASSERTION(result,"mNodeBuilder.Add returned null");}else{result=e->mNode;MOZ_ASSERT(result->mParticipant==aParticipant,"nsCycleCollectionParticipant shouldn't change!");}returnresult;}boolCCGraphBuilder::AddPurpleRoot(void*aRoot,nsCycleCollectionParticipant*aParti){CanonicalizeParticipant(&aRoot,&aParti);if(WantAllTraces()||!aParti->CanSkipInCC(aRoot)){PtrInfo*pinfo=AddNode(aRoot,aParti);if(!pinfo){returnfalse;}}returntrue;}voidCCGraphBuilder::DoneAddingRoots(){// We've finished adding roots, and everything in the graph is a root.mGraph.mRootCount=mGraph.MapCount();mCurrNode=newNodePool::Enumerator(mGraph.mNodes);}MOZ_NEVER_INLINEboolCCGraphBuilder::BuildGraph(SliceBudget&aBudget){constintptr_tkNumNodesBetweenTimeChecks=1000;constintptr_tkStep=SliceBudget::CounterReset/kNumNodesBetweenTimeChecks;MOZ_ASSERT(mCurrNode);while(!aBudget.isOverBudget()&&!mCurrNode->IsDone()){PtrInfo*pi=mCurrNode->GetNext();if(!pi){MOZ_CRASH();}mCurrPi=pi;// We need to call SetFirstChild() even on deleted nodes, to set their// firstChild() that may be read by a prior non-deleted neighbor.SetFirstChild();if(pi->mParticipant){nsresultrv=pi->mParticipant->TraverseNativeAndJS(pi->mPointer,*this);MOZ_RELEASE_ASSERT(!NS_FAILED(rv),"Cycle collector Traverse method failed");}if(mCurrNode->AtBlockEnd()){SetLastChild();}aBudget.step(kStep);}if(!mCurrNode->IsDone()){returnfalse;}if(mGraph.mRootCount>0){SetLastChild();}mCurrNode=nullptr;returntrue;}NS_IMETHODIMP_(void)CCGraphBuilder::NoteXPCOMRoot(nsISupports*aRoot){aRoot=CanonicalizeXPCOMParticipant(aRoot);NS_ASSERTION(aRoot,"Don't add objects that don't participate in collection!");nsXPCOMCycleCollectionParticipant*cp;ToParticipant(aRoot,&cp);NoteRoot(aRoot,cp);}NS_IMETHODIMP_(void)CCGraphBuilder::NoteJSRoot(JSObject*aRoot){if(JS::Zone*zone=MergeZone(JS::GCCellPtr(aRoot))){NoteRoot(zone,mJSZoneParticipant);}else{NoteRoot(aRoot,mJSParticipant);}}NS_IMETHODIMP_(void)CCGraphBuilder::NoteNativeRoot(void*aRoot,nsCycleCollectionParticipant*aParticipant){NoteRoot(aRoot,aParticipant);}NS_IMETHODIMP_(void)CCGraphBuilder::DescribeRefCountedNode(nsrefcntaRefCount,constchar*aObjName){MOZ_RELEASE_ASSERT(aRefCount!=0,"CCed refcounted object has zero refcount");MOZ_RELEASE_ASSERT(aRefCount!=UINT32_MAX,"CCed refcounted object has overflowing refcount");mResults.mVisitedRefCounted++;if(mLogger){mLogger->NoteRefCountedObject((uint64_t)mCurrPi->mPointer,aRefCount,aObjName);}mCurrPi->mRefCount=aRefCount;}NS_IMETHODIMP_(void)CCGraphBuilder::DescribeGCedNode(boolaIsMarked,constchar*aObjName,uint64_taCompartmentAddress){uint32_trefCount=aIsMarked?UINT32_MAX:0;mResults.mVisitedGCed++;if(mLogger){mLogger->NoteGCedObject((uint64_t)mCurrPi->mPointer,aIsMarked,aObjName,aCompartmentAddress);}mCurrPi->mRefCount=refCount;}NS_IMETHODIMP_(void)CCGraphBuilder::NoteXPCOMChild(nsISupports*aChild){nsCStringedgeName;if(WantDebugInfo()){edgeName.Assign(mNextEdgeName);mNextEdgeName.Truncate();}if(!aChild||!(aChild=CanonicalizeXPCOMParticipant(aChild))){return;}nsXPCOMCycleCollectionParticipant*cp;ToParticipant(aChild,&cp);if(cp&&(!cp->CanSkipThis(aChild)||WantAllTraces())){NoteChild(aChild,cp,edgeName);}}NS_IMETHODIMP_(void)CCGraphBuilder::NoteNativeChild(void*aChild,nsCycleCollectionParticipant*aParticipant){nsCStringedgeName;if(WantDebugInfo()){edgeName.Assign(mNextEdgeName);mNextEdgeName.Truncate();}if(!aChild){return;}MOZ_ASSERT(aParticipant,"Need a nsCycleCollectionParticipant!");if(!aParticipant->CanSkipThis(aChild)||WantAllTraces()){NoteChild(aChild,aParticipant,edgeName);}}NS_IMETHODIMP_(void)CCGraphBuilder::NoteJSChild(constJS::GCCellPtr&aChild){if(!aChild){return;}nsCStringedgeName;if(MOZ_UNLIKELY(WantDebugInfo())){edgeName.Assign(mNextEdgeName);mNextEdgeName.Truncate();}if(GCThingIsGrayCCThing(aChild)||MOZ_UNLIKELY(WantAllTraces())){if(JS::Zone*zone=MergeZone(aChild)){NoteChild(zone,mJSZoneParticipant,edgeName);}else{NoteChild(aChild.asCell(),mJSParticipant,edgeName);}}}NS_IMETHODIMP_(void)CCGraphBuilder::NoteNextEdgeName(constchar*aName){if(WantDebugInfo()){mNextEdgeName=aName;}}PtrInfo*CCGraphBuilder::AddWeakMapNode(JS::GCCellPtraNode){MOZ_ASSERT(aNode,"Weak map node should be non-null.");if(!GCThingIsGrayCCThing(aNode)&&!WantAllTraces()){returnnullptr;}if(JS::Zone*zone=MergeZone(aNode)){returnAddNode(zone,mJSZoneParticipant);}returnAddNode(aNode.asCell(),mJSParticipant);}PtrInfo*CCGraphBuilder::AddWeakMapNode(JSObject*aObject){returnAddWeakMapNode(JS::GCCellPtr(aObject));}NS_IMETHODIMP_(void)CCGraphBuilder::NoteWeakMapping(JSObject*aMap,JS::GCCellPtraKey,JSObject*aKdelegate,JS::GCCellPtraVal){// Don't try to optimize away the entry here, as we've already attempted to// do that in TraceWeakMapping in nsXPConnect.WeakMapping*mapping=mGraph.mWeakMaps.AppendElement();mapping->mMap=aMap?AddWeakMapNode(aMap):nullptr;mapping->mKey=aKey?AddWeakMapNode(aKey):nullptr;mapping->mKeyDelegate=aKdelegate?AddWeakMapNode(aKdelegate):mapping->mKey;mapping->mVal=aVal?AddWeakMapNode(aVal):nullptr;if(mLogger){mLogger->NoteWeakMapEntry((uint64_t)aMap,aKey?aKey.unsafeAsInteger():0,(uint64_t)aKdelegate,aVal?aVal.unsafeAsInteger():0);}}staticboolAddPurpleRoot(CCGraphBuilder&aBuilder,void*aRoot,nsCycleCollectionParticipant*aParti){returnaBuilder.AddPurpleRoot(aRoot,aParti);}// MayHaveChild() will be false after a Traverse if the object does// not have any children the CC will visit.classChildFinder:publicnsCycleCollectionTraversalCallback{public:ChildFinder():mMayHaveChild(false){}// The logic of the Note*Child functions must mirror that of their// respective functions in CCGraphBuilder.NS_IMETHOD_(void)NoteXPCOMChild(nsISupports*aChild);NS_IMETHOD_(void)NoteNativeChild(void*aChild,nsCycleCollectionParticipant*aHelper);NS_IMETHOD_(void)NoteJSChild(constJS::GCCellPtr&aThing);NS_IMETHOD_(void)DescribeRefCountedNode(nsrefcntaRefcount,constchar*aObjname){}NS_IMETHOD_(void)DescribeGCedNode(boolaIsMarked,constchar*aObjname,uint64_taCompartmentAddress){}NS_IMETHOD_(void)NoteNextEdgeName(constchar*aName){}boolMayHaveChild(){returnmMayHaveChild;}private:boolmMayHaveChild;};NS_IMETHODIMP_(void)ChildFinder::NoteXPCOMChild(nsISupports*aChild){if(!aChild||!(aChild=CanonicalizeXPCOMParticipant(aChild))){return;}nsXPCOMCycleCollectionParticipant*cp;ToParticipant(aChild,&cp);if(cp&&!cp->CanSkip(aChild,true)){mMayHaveChild=true;}}NS_IMETHODIMP_(void)ChildFinder::NoteNativeChild(void*aChild,nsCycleCollectionParticipant*aHelper){if(!aChild){return;}MOZ_ASSERT(aHelper,"Native child must have a participant");if(!aHelper->CanSkip(aChild,true)){mMayHaveChild=true;}}NS_IMETHODIMP_(void)ChildFinder::NoteJSChild(constJS::GCCellPtr&aChild){if(aChild&&JS::GCThingIsMarkedGray(aChild)){mMayHaveChild=true;}}staticboolMayHaveChild(void*aObj,nsCycleCollectionParticipant*aCp){ChildFindercf;aCp->TraverseNativeAndJS(aObj,cf);returncf.MayHaveChild();}// JSPurpleBuffer keeps references to GCThings which might affect the// next cycle collection. It is owned only by itself and during unlink its// self reference is broken down and the object ends up killing itself.// If GC happens before CC, references to GCthings and the self reference are// removed.classJSPurpleBuffer{~JSPurpleBuffer(){MOZ_ASSERT(mValues.IsEmpty());MOZ_ASSERT(mObjects.IsEmpty());}public:explicitJSPurpleBuffer(RefPtr<JSPurpleBuffer>&aReferenceToThis):mReferenceToThis(aReferenceToThis),mValues(kSegmentSize),mObjects(kSegmentSize){mReferenceToThis=this;mozilla::HoldJSObjects(this);}voidDestroy(){mReferenceToThis=nullptr;mValues.Clear();mObjects.Clear();mozilla::DropJSObjects(this);}NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(JSPurpleBuffer)NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(JSPurpleBuffer)RefPtr<JSPurpleBuffer>&mReferenceToThis;// These are raw pointers instead of Heap<T> because we only need Heap<T> for// pointers which may point into the nursery. The purple buffer never contains// pointers to the nursery because nursery gcthings can never be gray and only// gray things can be inserted into the purple buffer.staticconstsize_tkSegmentSize=512;SegmentedVector<JS::Value,kSegmentSize,InfallibleAllocPolicy>mValues;SegmentedVector<JSObject*,kSegmentSize,InfallibleAllocPolicy>mObjects;};NS_IMPL_CYCLE_COLLECTION_CLASS(JSPurpleBuffer)NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSPurpleBuffer)tmp->Destroy();NS_IMPL_CYCLE_COLLECTION_UNLINK_ENDNS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSPurpleBuffer)CycleCollectionNoteChild(cb,tmp,"self");NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END#define NS_TRACE_SEGMENTED_ARRAY(_field, _type) \ { \ for (auto iter = tmp->_field.Iter(); !iter.Done(); iter.Next()) { \ js::gc::CallTraceCallbackOnNonHeap<_type, TraceCallbacks>( \ &iter.Get(), aCallbacks, #_field, aClosure); \ } \ }NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(JSPurpleBuffer)NS_TRACE_SEGMENTED_ARRAY(mValues,JS::Value)NS_TRACE_SEGMENTED_ARRAY(mObjects,JSObject*)NS_IMPL_CYCLE_COLLECTION_TRACE_ENDNS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(JSPurpleBuffer,AddRef)NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(JSPurpleBuffer,Release)classSnowWhiteKiller:publicTraceCallbacks{structSnowWhiteObject{void*mPointer;nsCycleCollectionParticipant*mParticipant;nsCycleCollectingAutoRefCnt*mRefCnt;};// Segments are 4 KiB on 32-bit and 8 KiB on 64-bit.staticconstsize_tkSegmentSize=sizeof(void*)*1024;typedefSegmentedVector<SnowWhiteObject,kSegmentSize,InfallibleAllocPolicy>ObjectsVector;public:explicitSnowWhiteKiller(nsCycleCollector*aCollector):mCollector(aCollector),mObjects(kSegmentSize){MOZ_ASSERT(mCollector,"Calling SnowWhiteKiller after nsCC went away");}~SnowWhiteKiller(){for(autoiter=mObjects.Iter();!iter.Done();iter.Next()){SnowWhiteObject&o=iter.Get();if(!o.mRefCnt->get()&&!o.mRefCnt->IsInPurpleBuffer()){mCollector->RemoveObjectFromGraph(o.mPointer);o.mRefCnt->stabilizeForDeletion();{JS::AutoEnterCycleCollectionautocc(mCollector->Runtime()->Runtime());o.mParticipant->Trace(o.mPointer,*this,nullptr);}o.mParticipant->DeleteCycleCollectable(o.mPointer);}}}boolVisit(nsPurpleBuffer&aBuffer,nsPurpleBufferEntry*aEntry){MOZ_ASSERT(aEntry->mObject,"Null object in purple buffer");if(!aEntry->mRefCnt->get()){void*o=aEntry->mObject;nsCycleCollectionParticipant*cp=aEntry->mParticipant;CanonicalizeParticipant(&o,&cp);SnowWhiteObjectswo={o,cp,aEntry->mRefCnt};mObjects.InfallibleAppend(swo);aBuffer.Remove(aEntry);}returntrue;}boolHasSnowWhiteObjects()const{return!mObjects.IsEmpty();}virtualvoidTrace(JS::Heap<JS::Value>*aValue,constchar*aName,void*aClosure)constoverride{constJS::Value&val=aValue->unbarrieredGet();if(val.isGCThing()&&ValueIsGrayCCThing(val)){MOZ_ASSERT(!js::gc::IsInsideNursery(val.toGCThing()));mCollector->GetJSPurpleBuffer()->mValues.InfallibleAppend(val);}}virtualvoidTrace(JS::Heap<jsid>*aId,constchar*aName,void*aClosure)constoverride{}voidAppendJSObjectToPurpleBuffer(JSObject*obj)const{if(obj&&JS::ObjectIsMarkedGray(obj)){MOZ_ASSERT(JS::ObjectIsTenured(obj));mCollector->GetJSPurpleBuffer()->mObjects.InfallibleAppend(obj);}}virtualvoidTrace(JS::Heap<JSObject*>*aObject,constchar*aName,void*aClosure)constoverride{AppendJSObjectToPurpleBuffer(aObject->unbarrieredGet());}virtualvoidTrace(JSObject**aObject,constchar*aName,void*aClosure)constoverride{AppendJSObjectToPurpleBuffer(*aObject);}virtualvoidTrace(JS::TenuredHeap<JSObject*>*aObject,constchar*aName,void*aClosure)constoverride{AppendJSObjectToPurpleBuffer(aObject->unbarrieredGetPtr());}virtualvoidTrace(JS::Heap<JSString*>*aString,constchar*aName,void*aClosure)constoverride{}virtualvoidTrace(JS::Heap<JSScript*>*aScript,constchar*aName,void*aClosure)constoverride{}virtualvoidTrace(JS::Heap<JSFunction*>*aFunction,constchar*aName,void*aClosure)constoverride{}private:RefPtr<nsCycleCollector>mCollector;ObjectsVectormObjects;};classRemoveSkippableVisitor:publicSnowWhiteKiller{public:RemoveSkippableVisitor(nsCycleCollector*aCollector,js::SliceBudget&aBudget,boolaRemoveChildlessNodes,boolaAsyncSnowWhiteFreeing,CC_ForgetSkippableCallbackaCb):SnowWhiteKiller(aCollector),mBudget(aBudget),mRemoveChildlessNodes(aRemoveChildlessNodes),mAsyncSnowWhiteFreeing(aAsyncSnowWhiteFreeing),mDispatchedDeferredDeletion(false),mCallback(aCb){}~RemoveSkippableVisitor(){// Note, we must call the callback before SnowWhiteKiller calls// DeleteCycleCollectable!if(mCallback){mCallback();}if(HasSnowWhiteObjects()){// Effectively a continuation.nsCycleCollector_dispatchDeferredDeletion(true);}}boolVisit(nsPurpleBuffer&aBuffer,nsPurpleBufferEntry*aEntry){if(mBudget.isOverBudget()){returnfalse;}// CanSkip calls can be a bit slow, so increase the likelihood that// isOverBudget actually checks whether we're over the time budget.mBudget.step(5);MOZ_ASSERT(aEntry->mObject,"null mObject in purple buffer");if(!aEntry->mRefCnt->get()){if(!mAsyncSnowWhiteFreeing){SnowWhiteKiller::Visit(aBuffer,aEntry);}elseif(!mDispatchedDeferredDeletion){mDispatchedDeferredDeletion=true;nsCycleCollector_dispatchDeferredDeletion(false);}returntrue;}void*o=aEntry->mObject;nsCycleCollectionParticipant*cp=aEntry->mParticipant;CanonicalizeParticipant(&o,&cp);if(aEntry->mRefCnt->IsPurple()&&!cp->CanSkip(o,false)&&(!mRemoveChildlessNodes||MayHaveChild(o,cp))){returntrue;}aBuffer.Remove(aEntry);returntrue;}private:js::SliceBudget&mBudget;boolmRemoveChildlessNodes;boolmAsyncSnowWhiteFreeing;boolmDispatchedDeferredDeletion;CC_ForgetSkippableCallbackmCallback;};voidnsPurpleBuffer::RemoveSkippable(nsCycleCollector*aCollector,js::SliceBudget&aBudget,boolaRemoveChildlessNodes,boolaAsyncSnowWhiteFreeing,CC_ForgetSkippableCallbackaCb){RemoveSkippableVisitorvisitor(aCollector,aBudget,aRemoveChildlessNodes,aAsyncSnowWhiteFreeing,aCb);VisitEntries(visitor);}boolnsCycleCollector::FreeSnowWhite(boolaUntilNoSWInPurpleBuffer){CheckThreadSafety();if(mFreeingSnowWhite){returnfalse;}AutoRestore<bool>ar(mFreeingSnowWhite);mFreeingSnowWhite=true;boolhadSnowWhiteObjects=false;do{SnowWhiteKillervisitor(this);mPurpleBuf.VisitEntries(visitor);hadSnowWhiteObjects=hadSnowWhiteObjects||visitor.HasSnowWhiteObjects();if(!visitor.HasSnowWhiteObjects()){break;}}while(aUntilNoSWInPurpleBuffer);returnhadSnowWhiteObjects;}voidnsCycleCollector::ForgetSkippable(js::SliceBudget&aBudget,boolaRemoveChildlessNodes,boolaAsyncSnowWhiteFreeing){CheckThreadSafety();mozilla::Maybe<mozilla::AutoGlobalTimelineMarker>marker;if(NS_IsMainThread()){marker.emplace("nsCycleCollector::ForgetSkippable",MarkerStackRequest::NO_STACK);}// If we remove things from the purple buffer during graph building, we may// lose track of an object that was mutated during graph building.MOZ_ASSERT(IsIdle());if(mCCJSRuntime){mCCJSRuntime->PrepareForForgetSkippable();}MOZ_ASSERT(!mScanInProgress,"Don't forget skippable or free snow-white while scan is in progress.");mPurpleBuf.RemoveSkippable(this,aBudget,aRemoveChildlessNodes,aAsyncSnowWhiteFreeing,mForgetSkippableCB);}MOZ_NEVER_INLINEvoidnsCycleCollector::MarkRoots(SliceBudget&aBudget){JS::AutoAssertNoGCnogc;TimeLogtimeLog;AutoRestore<bool>ar(mScanInProgress);MOZ_RELEASE_ASSERT(!mScanInProgress);mScanInProgress=true;MOZ_ASSERT(mIncrementalPhase==GraphBuildingPhase);JS::AutoEnterCycleCollectionautocc(Runtime()->Runtime());booldoneBuilding=mBuilder->BuildGraph(aBudget);if(!doneBuilding){timeLog.Checkpoint("MarkRoots()");return;}mBuilder=nullptr;mIncrementalPhase=ScanAndCollectWhitePhase;timeLog.Checkpoint("MarkRoots()");}////////////////////////////////////////////////////////////////////////// Bacon & Rajan's |ScanRoots| routine.////////////////////////////////////////////////////////////////////////structScanBlackVisitor{ScanBlackVisitor(uint32_t&aWhiteNodeCount,bool&aFailed):mWhiteNodeCount(aWhiteNodeCount),mFailed(aFailed){}boolShouldVisitNode(PtrInfoconst*aPi){returnaPi->mColor!=black;}MOZ_NEVER_INLINEvoidVisitNode(PtrInfo*aPi){if(aPi->mColor==white){--mWhiteNodeCount;}aPi->mColor=black;}voidFailed(){mFailed=true;}private:uint32_t&mWhiteNodeCount;bool&mFailed;};staticvoidFloodBlackNode(uint32_t&aWhiteNodeCount,bool&aFailed,PtrInfo*aPi){GraphWalker<ScanBlackVisitor>(ScanBlackVisitor(aWhiteNodeCount,aFailed)).Walk(aPi);MOZ_ASSERT(aPi->mColor==black||!aPi->WasTraversed(),"FloodBlackNode should make aPi black");}// Iterate over the WeakMaps. If we mark anything while iterating// over the WeakMaps, we must iterate over all of the WeakMaps again.voidnsCycleCollector::ScanWeakMaps(){boolanyChanged;boolfailed=false;do{anyChanged=false;for(uint32_ti=0;i<mGraph.mWeakMaps.Length();i++){WeakMapping*wm=&mGraph.mWeakMaps[i];// If any of these are null, the original object was marked black.uint32_tmColor=wm->mMap?wm->mMap->mColor:black;uint32_tkColor=wm->mKey?wm->mKey->mColor:black;uint32_tkdColor=wm->mKeyDelegate?wm->mKeyDelegate->mColor:black;uint32_tvColor=wm->mVal?wm->mVal->mColor:black;MOZ_ASSERT(mColor!=grey,"Uncolored weak map");MOZ_ASSERT(kColor!=grey,"Uncolored weak map key");MOZ_ASSERT(kdColor!=grey,"Uncolored weak map key delegate");MOZ_ASSERT(vColor!=grey,"Uncolored weak map value");if(mColor==black&&kColor!=black&&kdColor==black){FloodBlackNode(mWhiteNodeCount,failed,wm->mKey);anyChanged=true;}if(mColor==black&&kColor==black&&vColor!=black){FloodBlackNode(mWhiteNodeCount,failed,wm->mVal);anyChanged=true;}}}while(anyChanged);if(failed){MOZ_ASSERT(false,"Ran out of memory in ScanWeakMaps");CC_TELEMETRY(_OOM,true);}}// Flood black from any objects in the purple buffer that are in the CC graph.classPurpleScanBlackVisitor{public:PurpleScanBlackVisitor(CCGraph&aGraph,nsCycleCollectorLogger*aLogger,uint32_t&aCount,bool&aFailed):mGraph(aGraph),mLogger(aLogger),mCount(aCount),mFailed(aFailed){}boolVisit(nsPurpleBuffer&aBuffer,nsPurpleBufferEntry*aEntry){MOZ_ASSERT(aEntry->mObject,"Entries with null mObject shouldn't be in the purple buffer.");MOZ_ASSERT(aEntry->mRefCnt->get()!=0,"Snow-white objects shouldn't be in the purple buffer.");void*obj=aEntry->mObject;if(!aEntry->mParticipant){obj=CanonicalizeXPCOMParticipant(static_cast<nsISupports*>(obj));MOZ_ASSERT(obj,"Don't add objects that don't participate in collection!");}PtrInfo*pi=mGraph.FindNode(obj);if(!pi){returntrue;}MOZ_ASSERT(pi->mParticipant,"No dead objects should be in the purple buffer.");if(MOZ_UNLIKELY(mLogger)){mLogger->NoteIncrementalRoot((uint64_t)pi->mPointer);}if(pi->mColor==black){returntrue;}FloodBlackNode(mCount,mFailed,pi);returntrue;}private:CCGraph&mGraph;RefPtr<nsCycleCollectorLogger>mLogger;uint32_t&mCount;bool&mFailed;};// Objects that have been stored somewhere since the start of incremental graph building must// be treated as live for this cycle collection, because we may not have accurate information// about who holds references to them.voidnsCycleCollector::ScanIncrementalRoots(){TimeLogtimeLog;// Reference counted objects:// We cleared the purple buffer at the start of the current ICC, so if a// refcounted object is purple, it may have been AddRef'd during the current// ICC. (It may also have only been released.) If that is the case, we cannot// be sure that the set of things pointing to the object in the CC graph// is accurate. Therefore, for safety, we treat any purple objects as being// live during the current CC. We don't remove anything from the purple// buffer here, so these objects will be suspected and freed in the next CC// if they are garbage.boolfailed=false;PurpleScanBlackVisitorpurpleScanBlackVisitor(mGraph,mLogger,mWhiteNodeCount,failed);mPurpleBuf.VisitEntries(purpleScanBlackVisitor);timeLog.Checkpoint("ScanIncrementalRoots::fix purple");boolhasJSRuntime=!!mCCJSRuntime;nsCycleCollectionParticipant*jsParticipant=hasJSRuntime?mCCJSRuntime->GCThingParticipant():nullptr;nsCycleCollectionParticipant*zoneParticipant=hasJSRuntime?mCCJSRuntime->ZoneParticipant():nullptr;boolhasLogger=!!mLogger;NodePool::Enumeratoretor(mGraph.mNodes);while(!etor.IsDone()){PtrInfo*pi=etor.GetNext();// As an optimization, if an object has already been determined to be live,// don't consider it further. We can't do this if there is a listener,// because the listener wants to know the complete set of incremental roots.if(pi->mColor==black&&MOZ_LIKELY(!hasLogger)){continue;}// Garbage collected objects:// If a GCed object was added to the graph with a refcount of zero, and is// now marked black by the GC, it was probably gray before and was exposed// to active JS, so it may have been stored somewhere, so it needs to be// treated as live.if(pi->IsGrayJS()&&MOZ_LIKELY(hasJSRuntime)){// If the object is still marked gray by the GC, nothing could have gotten// hold of it, so it isn't an incremental root.if(pi->mParticipant==jsParticipant){JS::GCCellPtrptr(pi->mPointer,JS::GCThingTraceKind(pi->mPointer));if(GCThingIsGrayCCThing(ptr)){continue;}}elseif(pi->mParticipant==zoneParticipant){JS::Zone*zone=static_cast<JS::Zone*>(pi->mPointer);if(js::ZoneGlobalsAreAllGray(zone)){continue;}}else{MOZ_ASSERT(false,"Non-JS thing with 0 refcount? Treating as live.");}}elseif(!pi->mParticipant&&pi->WasTraversed()){// Dead traversed refcounted objects:// If the object was traversed, it must have been alive at the start of// the CC, and thus had a positive refcount. It is dead now, so its// refcount must have decreased at some point during the CC. Therefore,// it would be in the purple buffer if it wasn't dead, so treat it as an// incremental root.//// This should not cause leaks because as the object died it should have// released anything it held onto, which will add them to the purple// buffer, which will cause them to be considered in the next CC.}else{continue;}// At this point, pi must be an incremental root.// If there's a listener, tell it about this root. We don't bother with the// optimization of skipping the Walk() if pi is black: it will just return// without doing anything and there's no need to make this case faster.if(MOZ_UNLIKELY(hasLogger)&&pi->mPointer){// Dead objects aren't logged. See bug 1031370.mLogger->NoteIncrementalRoot((uint64_t)pi->mPointer);}FloodBlackNode(mWhiteNodeCount,failed,pi);}timeLog.Checkpoint("ScanIncrementalRoots::fix nodes");if(failed){NS_ASSERTION(false,"Ran out of memory in ScanIncrementalRoots");CC_TELEMETRY(_OOM,true);}}// Mark nodes white and make sure their refcounts are ok.// No nodes are marked black during this pass to ensure that refcount// checking is run on all nodes not marked black by ScanIncrementalRoots.voidnsCycleCollector::ScanWhiteNodes(boolaFullySynchGraphBuild){NodePool::EnumeratornodeEnum(mGraph.mNodes);while(!nodeEnum.IsDone()){PtrInfo*pi=nodeEnum.GetNext();if(pi->mColor==black){// Incremental roots can be in a nonsensical state, so don't// check them. This will miss checking nodes that are merely// reachable from incremental roots.MOZ_ASSERT(!aFullySynchGraphBuild,"In a synch CC, no nodes should be marked black early on.");continue;}MOZ_ASSERT(pi->mColor==grey);if(!pi->WasTraversed()){// This node was deleted before it was traversed, so there's no reason// to look at it.MOZ_ASSERT(!pi->mParticipant,"Live nodes should all have been traversed");continue;}if(pi->mInternalRefs==pi->mRefCount||pi->IsGrayJS()){pi->mColor=white;++mWhiteNodeCount;continue;}if(pi->mInternalRefs>pi->mRefCount){#ifdef MOZ_CRASHREPORTERconstchar*piName="Unknown";if(pi->mParticipant){piName=pi->mParticipant->ClassName();}nsPrintfCStringmsg("More references to an object than its refcount, for class %s",piName);CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("CycleCollector"),msg);#endifMOZ_CRASH();}// This node will get marked black in the next pass.}}// Any remaining grey nodes that haven't already been deleted must be alive,// so mark them and their children black. Any nodes that are black must have// already had their children marked black, so there's no need to look at them// again. This pass may turn some white nodes to black.voidnsCycleCollector::ScanBlackNodes(){boolfailed=false;NodePool::EnumeratornodeEnum(mGraph.mNodes);while(!nodeEnum.IsDone()){PtrInfo*pi=nodeEnum.GetNext();if(pi->mColor==grey&&pi->WasTraversed()){FloodBlackNode(mWhiteNodeCount,failed,pi);}}if(failed){NS_ASSERTION(false,"Ran out of memory in ScanBlackNodes");CC_TELEMETRY(_OOM,true);}}voidnsCycleCollector::ScanRoots(boolaFullySynchGraphBuild){JS::AutoAssertNoGCnogc;AutoRestore<bool>ar(mScanInProgress);MOZ_RELEASE_ASSERT(!mScanInProgress);mScanInProgress=true;mWhiteNodeCount=0;MOZ_ASSERT(mIncrementalPhase==ScanAndCollectWhitePhase);JS::AutoEnterCycleCollectionautocc(Runtime()->Runtime());if(!aFullySynchGraphBuild){ScanIncrementalRoots();}TimeLogtimeLog;ScanWhiteNodes(aFullySynchGraphBuild);timeLog.Checkpoint("ScanRoots::ScanWhiteNodes");ScanBlackNodes();timeLog.Checkpoint("ScanRoots::ScanBlackNodes");// Scanning weak maps must be done last.ScanWeakMaps();timeLog.Checkpoint("ScanRoots::ScanWeakMaps");if(mLogger){mLogger->BeginResults();NodePool::Enumeratoretor(mGraph.mNodes);while(!etor.IsDone()){PtrInfo*pi=etor.GetNext();if(!pi->WasTraversed()){continue;}switch(pi->mColor){caseblack:if(!pi->IsGrayJS()&&!pi->IsBlackJS()&&pi->mInternalRefs!=pi->mRefCount){mLogger->DescribeRoot((uint64_t)pi->mPointer,pi->mInternalRefs);}break;casewhite:mLogger->DescribeGarbage((uint64_t)pi->mPointer);break;casegrey:MOZ_ASSERT(false,"All traversed objects should be black or white");break;}}mLogger->End();mLogger=nullptr;timeLog.Checkpoint("ScanRoots::listener");}}////////////////////////////////////////////////////////////////////////// Bacon & Rajan's |CollectWhite| routine, somewhat modified.////////////////////////////////////////////////////////////////////////boolnsCycleCollector::CollectWhite(){// Explanation of "somewhat modified": we have no way to collect the// set of whites "all at once", we have to ask each of them to drop// their outgoing links and assume this will cause the garbage cycle// to *mostly* self-destruct (except for the reference we continue// to hold).//// To do this "safely" we must make sure that the white nodes we're// operating on are stable for the duration of our operation. So we// make 3 sets of calls to language runtimes://// - Root(whites), which should pin the whites in memory.// - Unlink(whites), which drops outgoing links on each white.// - Unroot(whites), which returns the whites to normal GC.// Segments are 4 KiB on 32-bit and 8 KiB on 64-bit.staticconstsize_tkSegmentSize=sizeof(void*)*1024;SegmentedVector<PtrInfo*,kSegmentSize,InfallibleAllocPolicy>whiteNodes(kSegmentSize);TimeLogtimeLog;MOZ_ASSERT(mIncrementalPhase==ScanAndCollectWhitePhase);uint32_tnumWhiteNodes=0;uint32_tnumWhiteGCed=0;uint32_tnumWhiteJSZones=0;{JS::AutoAssertNoGCnogc;boolhasJSRuntime=!!mCCJSRuntime;nsCycleCollectionParticipant*zoneParticipant=hasJSRuntime?mCCJSRuntime->ZoneParticipant():nullptr;NodePool::Enumeratoretor(mGraph.mNodes);while(!etor.IsDone()){PtrInfo*pinfo=etor.GetNext();if(pinfo->mColor==white&&pinfo->mParticipant){if(pinfo->IsGrayJS()){MOZ_ASSERT(mCCJSRuntime);++numWhiteGCed;JS::Zone*zone;if(MOZ_UNLIKELY(pinfo->mParticipant==zoneParticipant)){++numWhiteJSZones;zone=static_cast<JS::Zone*>(pinfo->mPointer);}else{JS::GCCellPtrptr(pinfo->mPointer,JS::GCThingTraceKind(pinfo->mPointer));zone=JS::GetTenuredGCThingZone(ptr);}mCCJSRuntime->AddZoneWaitingForGC(zone);}else{whiteNodes.InfallibleAppend(pinfo);pinfo->mParticipant->Root(pinfo->mPointer);++numWhiteNodes;}}}}mResults.mFreedRefCounted+=numWhiteNodes;mResults.mFreedGCed+=numWhiteGCed;mResults.mFreedJSZones+=numWhiteJSZones;timeLog.Checkpoint("CollectWhite::Root");if(mBeforeUnlinkCB){mBeforeUnlinkCB();timeLog.Checkpoint("CollectWhite::BeforeUnlinkCB");}// Unlink() can trigger a GC, so do not touch any JS or anything// else not in whiteNodes after here.for(autoiter=whiteNodes.Iter();!iter.Done();iter.Next()){PtrInfo*pinfo=iter.Get();MOZ_ASSERT(pinfo->mParticipant,"Unlink shouldn't see objects removed from graph.");pinfo->mParticipant->Unlink(pinfo->mPointer);#ifdef DEBUGif(mCCJSRuntime){mCCJSRuntime->AssertNoObjectsToTrace(pinfo->mPointer);}#endif}timeLog.Checkpoint("CollectWhite::Unlink");JS::AutoAssertNoGCnogc;for(autoiter=whiteNodes.Iter();!iter.Done();iter.Next()){PtrInfo*pinfo=iter.Get();MOZ_ASSERT(pinfo->mParticipant,"Unroot shouldn't see objects removed from graph.");pinfo->mParticipant->Unroot(pinfo->mPointer);}timeLog.Checkpoint("CollectWhite::Unroot");nsCycleCollector_dispatchDeferredDeletion(false,true);timeLog.Checkpoint("CollectWhite::dispatchDeferredDeletion");mIncrementalPhase=CleanupPhase;returnnumWhiteNodes>0||numWhiteGCed>0||numWhiteJSZones>0;}////////////////////////// Memory reporting////////////////////////MOZ_DEFINE_MALLOC_SIZE_OF(CycleCollectorMallocSizeOf)NS_IMETHODIMPnsCycleCollector::CollectReports(nsIHandleReportCallback*aHandleReport,nsISupports*aData,boolaAnonymize){size_tobjectSize,graphSize,purpleBufferSize;SizeOfIncludingThis(CycleCollectorMallocSizeOf,&objectSize,&graphSize,&purpleBufferSize);if(objectSize>0){MOZ_COLLECT_REPORT("explicit/cycle-collector/collector-object",KIND_HEAP,UNITS_BYTES,objectSize,"Memory used for the cycle collector object itself.");}if(graphSize>0){MOZ_COLLECT_REPORT("explicit/cycle-collector/graph",KIND_HEAP,UNITS_BYTES,graphSize,"Memory used for the cycle collector's graph. This should be zero when ""the collector is idle.");}if(purpleBufferSize>0){MOZ_COLLECT_REPORT("explicit/cycle-collector/purple-buffer",KIND_HEAP,UNITS_BYTES,purpleBufferSize,"Memory used for the cycle collector's purple buffer.");}returnNS_OK;};////////////////////////////////////////////////////////////////////////// Collector implementation////////////////////////////////////////////////////////////////////////nsCycleCollector::nsCycleCollector():mActivelyCollecting(false),mFreeingSnowWhite(false),mScanInProgress(false),mCCJSRuntime(nullptr),mIncrementalPhase(IdlePhase),#ifdef DEBUGmEventTarget(GetCurrentThreadSerialEventTarget()),#endifmWhiteNodeCount(0),mBeforeUnlinkCB(nullptr),mForgetSkippableCB(nullptr),mUnmergedNeeded(0),mMergedInARow(0){}nsCycleCollector::~nsCycleCollector(){UnregisterWeakMemoryReporter(this);}voidnsCycleCollector::SetCCJSRuntime(CycleCollectedJSRuntime*aCCRuntime){MOZ_RELEASE_ASSERT(!mCCJSRuntime,"Multiple registrations of CycleCollectedJSRuntime in cycle collector");mCCJSRuntime=aCCRuntime;if(!NS_IsMainThread()){return;}// We can't register as a reporter in nsCycleCollector() because that runs// before the memory reporter manager is initialized. So we do it here// instead.RegisterWeakMemoryReporter(this);}voidnsCycleCollector::ClearCCJSRuntime(){MOZ_RELEASE_ASSERT(mCCJSRuntime,"Clearing CycleCollectedJSRuntime in cycle collector before a runtime was registered");mCCJSRuntime=nullptr;}#ifdef DEBUGstaticboolHasParticipant(void*aPtr,nsCycleCollectionParticipant*aParti){if(aParti){returntrue;}nsXPCOMCycleCollectionParticipant*xcp;ToParticipant(static_cast<nsISupports*>(aPtr),&xcp);returnxcp!=nullptr;}#endifMOZ_ALWAYS_INLINEvoidnsCycleCollector::Suspect(void*aPtr,nsCycleCollectionParticipant*aParti,nsCycleCollectingAutoRefCnt*aRefCnt){CheckThreadSafety();// Don't call AddRef or Release of a CCed object in a Traverse() method.MOZ_ASSERT(!mScanInProgress,"Attempted to call Suspect() while a scan was in progress");if(MOZ_UNLIKELY(mScanInProgress)){return;}MOZ_ASSERT(aPtr,"Don't suspect null pointers");MOZ_ASSERT(HasParticipant(aPtr,aParti),"Suspected nsISupports pointer must QI to nsXPCOMCycleCollectionParticipant");mPurpleBuf.Put(aPtr,aParti,aRefCnt);}voidnsCycleCollector::CheckThreadSafety(){#ifdef DEBUGMOZ_ASSERT(mEventTarget->IsOnCurrentThread());#endif}// The cycle collector uses the mark bitmap to discover what JS objects// were reachable only from XPConnect roots that might participate in// cycles. We ask the JS context whether we need to force a GC before// this CC. It returns true on startup (before the mark bits have been set),// and also when UnmarkGray has run out of stack. We also force GCs on shut// down to collect cycles involving both DOM and JS.voidnsCycleCollector::FixGrayBits(boolaForceGC,TimeLog&aTimeLog){CheckThreadSafety();if(!mCCJSRuntime){return;}if(!aForceGC){mCCJSRuntime->FixWeakMappingGrayBits();aTimeLog.Checkpoint("FixWeakMappingGrayBits");boolneedGC=!mCCJSRuntime->AreGCGrayBitsValid();// Only do a telemetry ping for non-shutdown CCs.CC_TELEMETRY(_NEED_GC,needGC);if(!needGC){return;}mResults.mForcedGC=true;}uint32_tcount=0;do{mCCJSRuntime->GarbageCollect(aForceGC?JS::gcreason::SHUTDOWN_CC:JS::gcreason::CC_FORCED);mCCJSRuntime->FixWeakMappingGrayBits();// It's possible that FixWeakMappingGrayBits will hit OOM when unmarking// gray and we will have to go round again. The second time there should not// be any weak mappings to fix up so the loop body should run at most twice.MOZ_RELEASE_ASSERT(count++<2);}while(!mCCJSRuntime->AreGCGrayBitsValid());aTimeLog.Checkpoint("FixGrayBits");}boolnsCycleCollector::IsIncrementalGCInProgress(){returnmCCJSRuntime&&JS::IsIncrementalGCInProgress(mCCJSRuntime->Runtime());}voidnsCycleCollector::FinishAnyIncrementalGCInProgress(){if(IsIncrementalGCInProgress()){NS_WARNING("Finishing incremental GC in progress during CC");JSContext*cx=CycleCollectedJSContext::Get()->Context();JS::PrepareForIncrementalGC(cx);JS::FinishIncrementalGC(cx,JS::gcreason::CC_FORCED);}}voidnsCycleCollector::CleanupAfterCollection(){TimeLogtimeLog;MOZ_ASSERT(mIncrementalPhase==CleanupPhase);MOZ_RELEASE_ASSERT(!mScanInProgress);mGraph.Clear();timeLog.Checkpoint("CleanupAfterCollection::mGraph.Clear()");uint32_tinterval=(uint32_t)((TimeStamp::Now()-mCollectionStart).ToMilliseconds());#ifdef COLLECT_TIME_DEBUGprintf("cc: total cycle collector time was %ums in %u slices\n",interval,mResults.mNumSlices);printf("cc: visited %u ref counted and %u GCed objects, freed %d ref counted and %d GCed objects",mResults.mVisitedRefCounted,mResults.mVisitedGCed,mResults.mFreedRefCounted,mResults.mFreedGCed);uint32_tnumVisited=mResults.mVisitedRefCounted+mResults.mVisitedGCed;if(numVisited>1000){uint32_tnumFreed=mResults.mFreedRefCounted+mResults.mFreedGCed;printf(" (%d%%)",100*numFreed/numVisited);}printf(".\ncc: \n");#endifCC_TELEMETRY(,interval);CC_TELEMETRY(_VISITED_REF_COUNTED,mResults.mVisitedRefCounted);CC_TELEMETRY(_VISITED_GCED,mResults.mVisitedGCed);CC_TELEMETRY(_COLLECTED,mWhiteNodeCount);timeLog.Checkpoint("CleanupAfterCollection::telemetry");if(mCCJSRuntime){mCCJSRuntime->FinalizeDeferredThings(mResults.mAnyManual?CycleCollectedJSContext::FinalizeNow:CycleCollectedJSContext::FinalizeIncrementally);mCCJSRuntime->EndCycleCollectionCallback(mResults);timeLog.Checkpoint("CleanupAfterCollection::EndCycleCollectionCallback()");}mIncrementalPhase=IdlePhase;}voidnsCycleCollector::ShutdownCollect(){FinishAnyIncrementalGCInProgress();SliceBudgetunlimitedBudget=SliceBudget::unlimited();uint32_ti;for(i=0;i<DEFAULT_SHUTDOWN_COLLECTIONS;++i){if(!Collect(ShutdownCC,unlimitedBudget,nullptr)){break;}}NS_WARNING_ASSERTION(i<NORMAL_SHUTDOWN_COLLECTIONS,"Extra shutdown CC");}staticvoidPrintPhase(constchar*aPhase){#ifdef DEBUG_PHASESprintf("cc: begin %s on %s\n",aPhase,NS_IsMainThread()?"mainthread":"worker");#endif}boolnsCycleCollector::Collect(ccTypeaCCType,SliceBudget&aBudget,nsICycleCollectorListener*aManualListener,boolaPreferShorterSlices){CheckThreadSafety();// This can legitimately happen in a few cases. See bug 383651.if(mActivelyCollecting||mFreeingSnowWhite){returnfalse;}mActivelyCollecting=true;MOZ_ASSERT(!IsIncrementalGCInProgress());mozilla::Maybe<mozilla::AutoGlobalTimelineMarker>marker;if(NS_IsMainThread()){marker.emplace("nsCycleCollector::Collect",MarkerStackRequest::NO_STACK);}boolstartedIdle=IsIdle();boolcollectedAny=false;// If the CC started idle, it will call BeginCollection, which// will do FreeSnowWhite, so it doesn't need to be done here.if(!startedIdle){TimeLogtimeLog;FreeSnowWhite(true);timeLog.Checkpoint("Collect::FreeSnowWhite");}if(aCCType!=SliceCC){mResults.mAnyManual=true;}++mResults.mNumSlices;boolcontinueSlice=aBudget.isUnlimited()||!aPreferShorterSlices;do{switch(mIncrementalPhase){caseIdlePhase:PrintPhase("BeginCollection");BeginCollection(aCCType,aManualListener);break;caseGraphBuildingPhase:PrintPhase("MarkRoots");MarkRoots(aBudget);// Only continue this slice if we're running synchronously or the// next phase will probably be short, to reduce the max pause for this// collection.// (There's no need to check if we've finished graph building, because// if we haven't, we've already exceeded our budget, and will finish// this slice anyways.)continueSlice=aBudget.isUnlimited()||(mResults.mNumSlices<3&&!aPreferShorterSlices);break;caseScanAndCollectWhitePhase:// We do ScanRoots and CollectWhite in a single slice to ensure// that we won't unlink a live object if a weak reference is// promoted to a strong reference after ScanRoots has finished.// See bug 926533.PrintPhase("ScanRoots");ScanRoots(startedIdle);PrintPhase("CollectWhite");collectedAny=CollectWhite();break;caseCleanupPhase:PrintPhase("CleanupAfterCollection");CleanupAfterCollection();continueSlice=false;break;}if(continueSlice){// Force SliceBudget::isOverBudget to check the time.aBudget.step(SliceBudget::CounterReset);continueSlice=!aBudget.isOverBudget();}}while(continueSlice);// Clear mActivelyCollecting here to ensure that a recursive call to// Collect() does something.mActivelyCollecting=false;if(aCCType!=SliceCC&&!startedIdle){// We were in the middle of an incremental CC (using its own listener).// Somebody has forced a CC, so after having finished out the current CC,// run the CC again using the new listener.MOZ_ASSERT(IsIdle());if(Collect(aCCType,aBudget,aManualListener)){collectedAny=true;}}MOZ_ASSERT_IF(aCCType!=SliceCC,IsIdle());returncollectedAny;}// Any JS objects we have in the graph could die when we GC, but we// don't want to abandon the current CC, because the graph contains// information about purple roots. So we synchronously finish off// the current CC.voidnsCycleCollector::PrepareForGarbageCollection(){if(IsIdle()){MOZ_ASSERT(mGraph.IsEmpty(),"Non-empty graph when idle");MOZ_ASSERT(!mBuilder,"Non-null builder when idle");if(mJSPurpleBuffer){mJSPurpleBuffer->Destroy();}return;}FinishAnyCurrentCollection();}voidnsCycleCollector::FinishAnyCurrentCollection(){if(IsIdle()){return;}SliceBudgetunlimitedBudget=SliceBudget::unlimited();PrintPhase("FinishAnyCurrentCollection");// Use SliceCC because we only want to finish the CC in progress.Collect(SliceCC,unlimitedBudget,nullptr);// It is only okay for Collect() to have failed to finish the// current CC if we're reentering the CC at some point past// graph building. We need to be past the point where the CC will// look at JS objects so that it is safe to GC.MOZ_ASSERT(IsIdle()||(mActivelyCollecting&&mIncrementalPhase!=GraphBuildingPhase),"Reentered CC during graph building");}// Don't merge too many times in a row, and do at least a minimum// number of unmerged CCs in a row.staticconstuint32_tkMinConsecutiveUnmerged=3;staticconstuint32_tkMaxConsecutiveMerged=3;boolnsCycleCollector::ShouldMergeZones(ccTypeaCCType){if(!mCCJSRuntime){returnfalse;}MOZ_ASSERT(mUnmergedNeeded<=kMinConsecutiveUnmerged);MOZ_ASSERT(mMergedInARow<=kMaxConsecutiveMerged);if(mMergedInARow==kMaxConsecutiveMerged){MOZ_ASSERT(mUnmergedNeeded==0);mUnmergedNeeded=kMinConsecutiveUnmerged;}if(mUnmergedNeeded>0){mUnmergedNeeded--;mMergedInARow=0;returnfalse;}if(aCCType==SliceCC&&mCCJSRuntime->UsefulToMergeZones()){mMergedInARow++;returntrue;}else{mMergedInARow=0;returnfalse;}}voidnsCycleCollector::BeginCollection(ccTypeaCCType,nsICycleCollectorListener*aManualListener){TimeLogtimeLog;MOZ_ASSERT(IsIdle());MOZ_RELEASE_ASSERT(!mScanInProgress);mCollectionStart=TimeStamp::Now();if(mCCJSRuntime){mCCJSRuntime->BeginCycleCollectionCallback();timeLog.Checkpoint("BeginCycleCollectionCallback()");}boolisShutdown=(aCCType==ShutdownCC);// Set up the listener for this CC.MOZ_ASSERT_IF(isShutdown,!aManualListener);MOZ_ASSERT(!mLogger,"Forgot to clear a previous listener?");if(aManualListener){aManualListener->AsLogger(getter_AddRefs(mLogger));}aManualListener=nullptr;if(!mLogger&&mParams.LogThisCC(isShutdown)){mLogger=newnsCycleCollectorLogger();if(mParams.AllTracesThisCC(isShutdown)){mLogger->SetAllTraces();}}// On a WantAllTraces CC, force a synchronous global GC to prevent// hijinks from ForgetSkippable and compartmental GCs.boolforceGC=isShutdown||(mLogger&&mLogger->IsAllTraces());// BeginCycleCollectionCallback() might have started an IGC, and we need// to finish it before we run FixGrayBits.FinishAnyIncrementalGCInProgress();timeLog.Checkpoint("Pre-FixGrayBits finish IGC");FixGrayBits(forceGC,timeLog);if(mCCJSRuntime){mCCJSRuntime->CheckGrayBits();}FreeSnowWhite(true);timeLog.Checkpoint("BeginCollection FreeSnowWhite");if(mLogger&&NS_FAILED(mLogger->Begin())){mLogger=nullptr;}// FreeSnowWhite could potentially have started an IGC, which we need// to finish before we look at any JS roots.FinishAnyIncrementalGCInProgress();timeLog.Checkpoint("Post-FreeSnowWhite finish IGC");// Set up the data structures for building the graph.JS::AutoAssertNoGCnogc;JS::AutoEnterCycleCollectionautocc(mCCJSRuntime->Runtime());mGraph.Init();mResults.Init();mResults.mAnyManual=(aCCType!=SliceCC);boolmergeZones=ShouldMergeZones(aCCType);mResults.mMergedZones=mergeZones;MOZ_ASSERT(!mBuilder,"Forgot to clear mBuilder");mBuilder=newCCGraphBuilder(mGraph,mResults,mCCJSRuntime,mLogger,mergeZones);timeLog.Checkpoint("BeginCollection prepare graph builder");if(mCCJSRuntime){mCCJSRuntime->TraverseRoots(*mBuilder);timeLog.Checkpoint("mJSContext->TraverseRoots()");}AutoRestore<bool>ar(mScanInProgress);MOZ_RELEASE_ASSERT(!mScanInProgress);mScanInProgress=true;mPurpleBuf.SelectPointers(*mBuilder);timeLog.Checkpoint("SelectPointers()");mBuilder->DoneAddingRoots();mIncrementalPhase=GraphBuildingPhase;}uint32_tnsCycleCollector::SuspectedCount(){CheckThreadSafety();returnmPurpleBuf.Count();}voidnsCycleCollector::Shutdown(boolaDoCollect){CheckThreadSafety();// Always delete snow white objects.FreeSnowWhite(true);if(aDoCollect){ShutdownCollect();}}voidnsCycleCollector::RemoveObjectFromGraph(void*aObj){if(IsIdle()){return;}mGraph.RemoveObjectFromMap(aObj);}voidnsCycleCollector::SizeOfIncludingThis(mozilla::MallocSizeOfaMallocSizeOf,size_t*aObjectSize,size_t*aGraphSize,size_t*aPurpleBufferSize)const{*aObjectSize=aMallocSizeOf(this);*aGraphSize=mGraph.SizeOfExcludingThis(aMallocSizeOf);*aPurpleBufferSize=mPurpleBuf.SizeOfExcludingThis(aMallocSizeOf);// These fields are deliberately not measured:// - mCCJSRuntime: because it's non-owning and measured by JS reporters.// - mParams: because it only contains scalars.}JSPurpleBuffer*nsCycleCollector::GetJSPurpleBuffer(){if(!mJSPurpleBuffer){// The Release call here confuses the GC analysis.JS::AutoSuppressGCAnalysisnogc;// JSPurpleBuffer keeps itself alive, but we need to create it in such way// that it ends up in the normal purple buffer. That happens when// nsRefPtr goes out of the scope and calls Release.RefPtr<JSPurpleBuffer>pb=newJSPurpleBuffer(mJSPurpleBuffer);}returnmJSPurpleBuffer;}////////////////////////////////////////////////////////////////////////// Module public API (exported in nsCycleCollector.h)// Just functions that redirect into the singleton, once it's built.////////////////////////////////////////////////////////////////////////voidnsCycleCollector_registerJSContext(CycleCollectedJSContext*aCx){CollectorData*data=sCollectorData.get();// We should have started the cycle collector by now.MOZ_ASSERT(data);MOZ_ASSERT(data->mCollector);// But we shouldn't already have a context.MOZ_ASSERT(!data->mContext);data->mContext=aCx;data->mCollector->SetCCJSRuntime(aCx->Runtime());}voidnsCycleCollector_forgetJSContext(){CollectorData*data=sCollectorData.get();// We should have started the cycle collector by now.MOZ_ASSERT(data);// And we shouldn't have already forgotten our context.MOZ_ASSERT(data->mContext);// But it may have shutdown already.if(data->mCollector){data->mCollector->ClearCCJSRuntime();data->mContext=nullptr;}else{data->mContext=nullptr;deletedata;sCollectorData.set(nullptr);}}/* static */CycleCollectedJSContext*CycleCollectedJSContext::Get(){CollectorData*data=sCollectorData.get();if(data){returndata->mContext;}returnnullptr;}MOZ_NEVER_INLINEstaticvoidSuspectAfterShutdown(void*aPtr,nsCycleCollectionParticipant*aCp,nsCycleCollectingAutoRefCnt*aRefCnt,bool*aShouldDelete){if(aRefCnt->get()==0){if(!aShouldDelete){// The CC is shut down, so we can't be in the middle of an ICC.CanonicalizeParticipant(&aPtr,&aCp);aRefCnt->stabilizeForDeletion();aCp->DeleteCycleCollectable(aPtr);}else{*aShouldDelete=true;}}else{// Make sure we'll get called again.aRefCnt->RemoveFromPurpleBuffer();}}voidNS_CycleCollectorSuspect3(void*aPtr,nsCycleCollectionParticipant*aCp,nsCycleCollectingAutoRefCnt*aRefCnt,bool*aShouldDelete){CollectorData*data=sCollectorData.get();// We should have started the cycle collector by now.MOZ_ASSERT(data);if(MOZ_LIKELY(data->mCollector)){data->mCollector->Suspect(aPtr,aCp,aRefCnt);return;}SuspectAfterShutdown(aPtr,aCp,aRefCnt,aShouldDelete);}uint32_tnsCycleCollector_suspectedCount(){CollectorData*data=sCollectorData.get();// We should have started the cycle collector by now.MOZ_ASSERT(data);if(!data->mCollector){return0;}returndata->mCollector->SuspectedCount();}boolnsCycleCollector_init(){#ifdef DEBUGstaticboolsInitialized;MOZ_ASSERT(NS_IsMainThread(),"Wrong thread!");MOZ_ASSERT(!sInitialized,"Called twice!?");sInitialized=true;#endifreturnsCollectorData.init();}staticnsCycleCollector*gMainThreadCollector;voidnsCycleCollector_startup(){if(sCollectorData.get()){MOZ_CRASH();}CollectorData*data=newCollectorData;data->mCollector=newnsCycleCollector();data->mContext=nullptr;sCollectorData.set(data);if(NS_IsMainThread()){MOZ_ASSERT(!gMainThreadCollector);gMainThreadCollector=data->mCollector;}}voidnsCycleCollector_registerNonPrimaryContext(CycleCollectedJSContext*aCx){if(sCollectorData.get()){MOZ_CRASH();}MOZ_ASSERT(gMainThreadCollector);CollectorData*data=newCollectorData;data->mCollector=gMainThreadCollector;data->mContext=aCx;sCollectorData.set(data);}voidnsCycleCollector_forgetNonPrimaryContext(){CollectorData*data=sCollectorData.get();// We should have started the cycle collector by now.MOZ_ASSERT(data);// And we shouldn't have already forgotten our context.MOZ_ASSERT(data->mContext);// We should not have shut down the cycle collector yet.MOZ_ASSERT(data->mCollector);deletedata;sCollectorData.set(nullptr);}voidnsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallbackaCB){CollectorData*data=sCollectorData.get();// We should have started the cycle collector by now.MOZ_ASSERT(data);MOZ_ASSERT(data->mCollector);data->mCollector->SetBeforeUnlinkCallback(aCB);}voidnsCycleCollector_setForgetSkippableCallback(CC_ForgetSkippableCallbackaCB){CollectorData*data=sCollectorData.get();// We should have started the cycle collector by now.MOZ_ASSERT(data);MOZ_ASSERT(data->mCollector);data->mCollector->SetForgetSkippableCallback(aCB);}voidnsCycleCollector_forgetSkippable(js::SliceBudget&aBudget,boolaRemoveChildlessNodes,boolaAsyncSnowWhiteFreeing){CollectorData*data=sCollectorData.get();// We should have started the cycle collector by now.MOZ_ASSERT(data);MOZ_ASSERT(data->mCollector);AUTO_PROFILER_LABEL("nsCycleCollector_forgetSkippable",CC);TimeLogtimeLog;data->mCollector->ForgetSkippable(aBudget,aRemoveChildlessNodes,aAsyncSnowWhiteFreeing);timeLog.Checkpoint("ForgetSkippable()");}voidnsCycleCollector_dispatchDeferredDeletion(boolaContinuation,boolaPurge){CycleCollectedJSRuntime*rt=CycleCollectedJSRuntime::Get();if(rt){rt->DispatchDeferredDeletion(aContinuation,aPurge);}}boolnsCycleCollector_doDeferredDeletion(){CollectorData*data=sCollectorData.get();// We should have started the cycle collector by now.MOZ_ASSERT(data);MOZ_ASSERT(data->mCollector);MOZ_ASSERT(data->mContext);returndata->mCollector->FreeSnowWhite(false);}already_AddRefed<nsICycleCollectorLogSink>nsCycleCollector_createLogSink(){nsCOMPtr<nsICycleCollectorLogSink>sink=newnsCycleCollectorLogSinkToFile();returnsink.forget();}voidnsCycleCollector_collect(nsICycleCollectorListener*aManualListener){CollectorData*data=sCollectorData.get();// We should have started the cycle collector by now.MOZ_ASSERT(data);MOZ_ASSERT(data->mCollector);AUTO_PROFILER_LABEL("nsCycleCollector_collect",CC);SliceBudgetunlimitedBudget=SliceBudget::unlimited();data->mCollector->Collect(ManualCC,unlimitedBudget,aManualListener);}voidnsCycleCollector_collectSlice(SliceBudget&budget,boolaPreferShorterSlices){CollectorData*data=sCollectorData.get();// We should have started the cycle collector by now.MOZ_ASSERT(data);MOZ_ASSERT(data->mCollector);AUTO_PROFILER_LABEL("nsCycleCollector_collectSlice",CC);data->mCollector->Collect(SliceCC,budget,nullptr,aPreferShorterSlices);}voidnsCycleCollector_prepareForGarbageCollection(){CollectorData*data=sCollectorData.get();MOZ_ASSERT(data);if(!data->mCollector){return;}data->mCollector->PrepareForGarbageCollection();}voidnsCycleCollector_finishAnyCurrentCollection(){CollectorData*data=sCollectorData.get();MOZ_ASSERT(data);if(!data->mCollector){return;}data->mCollector->FinishAnyCurrentCollection();}voidnsCycleCollector_shutdown(boolaDoCollect){CollectorData*data=sCollectorData.get();if(data){MOZ_ASSERT(data->mCollector);AUTO_PROFILER_LABEL("nsCycleCollector_shutdown",CC);if(gMainThreadCollector==data->mCollector){gMainThreadCollector=nullptr;}data->mCollector->Shutdown(aDoCollect);data->mCollector=nullptr;if(data->mContext){// Run any remaining tasks that may have been enqueued via// RunInStableState during the final cycle collection.data->mContext->ProcessStableStateQueue();}if(!data->mContext){deletedata;sCollectorData.set(nullptr);}}}